Commit 56687a08 by Agnès Toulet Committed by GitHub

Core: use go-datemath in timerange utility (#23120)

* Core: use go-datemath in time_range

* Core: update timerange for negative epoch

* Core: update gtime component

* Core: clean up time_range tests

* Update pkg/components/gtime/gtime.go

Co-Authored-By: Arve Knudsen <arve.knudsen@gmail.com>

* Core: clean gtime tests

* components/gtime: Fix test

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
parent 69bdcccd
package gtime package gtime
import ( import (
"fmt"
"regexp" "regexp"
"strconv" "strconv"
"time" "time"
) )
var dateUnitPattern = regexp.MustCompile(`(\d+)([wdy])`) var dateUnitPattern = regexp.MustCompile(`^(\d+)([dwMy])$`)
// ParseInterval parses an interval with support for all units that Grafana uses. // ParseInterval parses an interval with support for all units that Grafana uses.
func ParseInterval(interval string) (time.Duration, error) { func ParseInterval(interval string) (time.Duration, error) {
...@@ -18,14 +19,18 @@ func ParseInterval(interval string) (time.Duration, error) { ...@@ -18,14 +19,18 @@ func ParseInterval(interval string) (time.Duration, error) {
num, _ := strconv.Atoi(string(result[1])) num, _ := strconv.Atoi(string(result[1]))
period := string(result[2]) period := string(result[2])
now := time.Now()
if period == `d` {
return time.Hour * 24 * time.Duration(num), nil switch period {
} case "d":
return now.Sub(now.AddDate(0, 0, -num)), nil
if period == `w` { case "w":
return time.Hour * 24 * 7 * time.Duration(num), nil return now.Sub(now.AddDate(0, 0, -num*7)), nil
case "M":
return now.Sub(now.AddDate(0, -num, 0)), nil
case "y":
return now.Sub(now.AddDate(-num, 0, 0)), nil
} }
return time.Hour * 24 * 7 * 365 * time.Duration(num), nil return 0, fmt.Errorf("ParseInterval: invalid duration %q", interval)
} }
package gtime package gtime
import ( import (
"errors"
"fmt" "fmt"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require"
) )
func TestParseInterval(t *testing.T) { func TestParseInterval(t *testing.T) {
now := time.Now()
tcs := []struct { tcs := []struct {
interval string interval string
duration time.Duration duration time.Duration
err error err string
}{ }{
{interval: "1d", duration: time.Hour * 24}, {interval: "1d", duration: now.Sub(now.AddDate(0, 0, -1))},
{interval: "1w", duration: time.Hour * 24 * 7}, {interval: "1w", duration: now.Sub(now.AddDate(0, 0, -7))},
{interval: "2w", duration: time.Hour * 24 * 7 * 2}, {interval: "2w", duration: now.Sub(now.AddDate(0, 0, -14))},
{interval: "1y", duration: time.Hour * 24 * 7 * 365}, {interval: "1M", duration: now.Sub(now.AddDate(0, -1, 0))},
{interval: "5y", duration: time.Hour * 24 * 7 * 365 * 5}, {interval: "1y", duration: now.Sub(now.AddDate(-1, 0, 0))},
{interval: "1M", err: errors.New("time: unknown unit M in duration 1M")}, {interval: "5y", duration: now.Sub(now.AddDate(-5, 0, 0))},
{interval: "invalid-duration", err: errors.New("time: invalid duration invalid-duration")}, {interval: "invalid-duration", err: "time: invalid duration invalid-duration"},
} }
for i, tc := range tcs { for i, tc := range tcs {
t.Run(fmt.Sprintf("testcase %d", i), func(t *testing.T) { t.Run(fmt.Sprintf("testcase %d", i), func(t *testing.T) {
res, err := ParseInterval(tc.interval) res, err := ParseInterval(tc.interval)
if err != nil && err.Error() != tc.err.Error() { if tc.err == "" {
t.Fatalf("expected '%v' got '%v'", tc.err, err) require.NoError(t, err, "interval %q", tc.interval)
} require.Equal(t, tc.duration, res, "interval %q", tc.interval)
if res != tc.duration { } else {
t.Errorf("expected %v got %v", tc.duration, res) require.EqualError(t, err, tc.err, "interval %q", tc.interval)
} }
}) })
} }
......
package tsdb package tsdb
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/timberio/go-datemath"
) )
func NewTimeRange(from, to string) *TimeRange { func NewTimeRange(from, to string) *TimeRange {
...@@ -79,38 +80,26 @@ func tryParseUnixMsEpoch(val string) (time.Time, bool) { ...@@ -79,38 +80,26 @@ func tryParseUnixMsEpoch(val string) (time.Time, bool) {
} }
func (tr *TimeRange) ParseFrom() (time.Time, error) { func (tr *TimeRange) ParseFrom() (time.Time, error) {
if res, ok := tryParseUnixMsEpoch(tr.From); ok { return parse(tr.From, tr.now, false)
return res, nil
}
fromRaw := strings.Replace(tr.From, "now-", "", 1)
diff, err := time.ParseDuration("-" + fromRaw)
if err != nil {
return time.Time{}, err
}
return tr.now.Add(diff), nil
} }
func (tr *TimeRange) ParseTo() (time.Time, error) { func (tr *TimeRange) ParseTo() (time.Time, error) {
if tr.To == "now" { return parse(tr.To, tr.now, true)
return tr.now, nil }
} else if strings.HasPrefix(tr.To, "now-") {
withoutNow := strings.Replace(tr.To, "now-", "", 1)
diff, err := time.ParseDuration("-" + withoutNow)
if err != nil {
return time.Time{}, nil
}
return tr.now.Add(diff), nil func parse(s string, now time.Time, withRoundUp bool) (time.Time, error) {
if res, ok := tryParseUnixMsEpoch(s); ok {
return res, nil
} }
if res, ok := tryParseUnixMsEpoch(tr.To); ok { withoutNow := strings.Replace(s, "now-", "", 1)
return res, nil
diff, err := time.ParseDuration("-" + withoutNow)
if err != nil {
return datemath.ParseAndEvaluate(s, datemath.WithNow(now), datemath.WithRoundUp(withRoundUp))
} }
return time.Time{}, fmt.Errorf("cannot parse to value %s", tr.To) return now.Add(diff), nil
} }
// EpochPrecisionToMs converts epoch precision to millisecond, if needed. // EpochPrecisionToMs converts epoch precision to millisecond, if needed.
......
package tsdb package tsdb
import ( import (
"strconv"
"testing" "testing"
"time" "time"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
...@@ -20,7 +22,8 @@ func TestTimeRange(t *testing.T) { ...@@ -20,7 +22,8 @@ func TestTimeRange(t *testing.T) {
} }
Convey("5m ago ", func() { Convey("5m ago ", func() {
fiveMinAgo, _ := time.ParseDuration("-5m") fiveMinAgo, err := time.ParseDuration("-5m")
So(err, ShouldBeNil)
expected := now.Add(fiveMinAgo) expected := now.Add(fiveMinAgo)
res, err := tr.ParseFrom() res, err := tr.ParseFrom()
...@@ -43,7 +46,8 @@ func TestTimeRange(t *testing.T) { ...@@ -43,7 +46,8 @@ func TestTimeRange(t *testing.T) {
} }
Convey("5h ago ", func() { Convey("5h ago ", func() {
fiveHourAgo, _ := time.ParseDuration("-5h") fiveHourAgo, err := time.ParseDuration("-5h")
So(err, ShouldBeNil)
expected := now.Add(fiveHourAgo) expected := now.Add(fiveHourAgo)
res, err := tr.ParseFrom() res, err := tr.ParseFrom()
...@@ -52,7 +56,8 @@ func TestTimeRange(t *testing.T) { ...@@ -52,7 +56,8 @@ func TestTimeRange(t *testing.T) {
}) })
Convey("now-10m ", func() { Convey("now-10m ", func() {
tenMinAgo, _ := time.ParseDuration("-10m") tenMinAgo, err := time.ParseDuration("-10m")
So(err, ShouldBeNil)
expected := now.Add(tenMinAgo) expected := now.Add(tenMinAgo)
res, err := tr.ParseTo() res, err := tr.ParseTo()
So(err, ShouldBeNil) So(err, ShouldBeNil)
...@@ -60,7 +65,101 @@ func TestTimeRange(t *testing.T) { ...@@ -60,7 +65,101 @@ func TestTimeRange(t *testing.T) {
}) })
}) })
Convey("can parse unix epocs", func() { now, err := time.Parse(time.RFC3339Nano, "2020-03-26T15:12:56.000Z")
So(err, ShouldBeNil)
Convey("Can parse now-1M/M, now-1M/M", func() {
tr := TimeRange{
From: "now-1M/M",
To: "now-1M/M",
now: now,
}
Convey("from now-1M/M ", func() {
expected, err := time.Parse(time.RFC3339Nano, "2020-02-01T00:00:00.000Z")
So(err, ShouldBeNil)
res, err := tr.ParseFrom()
So(err, ShouldBeNil)
So(res, ShouldEqual, expected)
})
Convey("to now-1M/M ", func() {
expected, err := time.Parse(time.RFC3339Nano, "2020-02-29T23:59:59.999Z")
So(err, ShouldBeNil)
res, err := tr.ParseTo()
So(err, ShouldBeNil)
So(res, ShouldEqual, expected)
})
})
Convey("Can parse now-3d, now+3w", func() {
tr := TimeRange{
From: "now-3d",
To: "now+3w",
now: now,
}
Convey("now-3d ", func() {
expected, err := time.Parse(time.RFC3339Nano, "2020-03-23T15:12:56.000Z")
So(err, ShouldBeNil)
res, err := tr.ParseFrom()
So(err, ShouldBeNil)
So(res, ShouldEqual, expected)
})
Convey("now+3w ", func() {
expected, err := time.Parse(time.RFC3339Nano, "2020-04-16T15:12:56.000Z")
So(err, ShouldBeNil)
res, err := tr.ParseTo()
So(err, ShouldBeNil)
So(res, ShouldEqual, expected)
})
})
Convey("Can parse 1960-02-01T07:00:00.000Z, 1965-02-03T08:00:00.000Z", func() {
tr := TimeRange{
From: "1960-02-01T07:00:00.000Z",
To: "1965-02-03T08:00:00.000Z",
now: now,
}
Convey("1960-02-01T07:00:00.000Z ", func() {
expected, err := time.Parse(time.RFC3339Nano, "1960-02-01T07:00:00.000Z")
So(err, ShouldBeNil)
res, err := tr.ParseFrom()
So(err, ShouldBeNil)
So(res, ShouldEqual, expected)
})
Convey("1965-02-03T08:00:00.000Z ", func() {
expected, err := time.Parse(time.RFC3339Nano, "1965-02-03T08:00:00.000Z")
So(err, ShouldBeNil)
res, err := tr.ParseTo()
So(err, ShouldBeNil)
So(res, ShouldEqual, expected)
})
})
Convey("Can parse negative unix epochs", func() {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1965, 2, 3, 8, 0, 0, 0, time.UTC)
tr := NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
res, err := tr.ParseFrom()
So(err, ShouldBeNil)
So(res, ShouldEqual, from)
res, err = tr.ParseTo()
So(err, ShouldBeNil)
So(res, ShouldEqual, to)
})
Convey("can parse unix epochs", func() {
var err error var err error
tr := TimeRange{ tr := TimeRange{
From: "1474973725473", From: "1474973725473",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment