diff --git a/app/vmctl/utils/time.go b/app/vmctl/utils/time.go index 541a84f45..9b3b496b4 100644 --- a/app/vmctl/utils/time.go +++ b/app/vmctl/utils/time.go @@ -2,8 +2,6 @@ package utils import ( "fmt" - "strconv" - "strings" "time" "github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils" @@ -15,81 +13,9 @@ const ( maxTimeMsecs = int64(1<<63-1) / 1e6 ) -func parseTime(s string) (float64, error) { - if len(s) > 0 && (s[len(s)-1] != 'Z' && s[len(s)-1] > '9' || s[0] == '-') { - // Parse duration relative to the current time - d, err := promutils.ParseDuration(s) - if err != nil { - return 0, err - } - if d > 0 { - d = -d - } - t := time.Now().Add(d) - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 4 { - // Parse YYYY - t, err := time.Parse("2006", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if !strings.Contains(s, "-") { - // Parse the timestamp in milliseconds - return strconv.ParseFloat(s, 64) - } - if len(s) == 7 { - // Parse YYYY-MM - t, err := time.Parse("2006-01", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 10 { - // Parse YYYY-MM-DD - t, err := time.Parse("2006-01-02", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 13 { - // Parse YYYY-MM-DDTHH - t, err := time.Parse("2006-01-02T15", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 16 { - // Parse YYYY-MM-DDTHH:MM - t, err := time.Parse("2006-01-02T15:04", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 19 { - // Parse YYYY-MM-DDTHH:MM:SS - t, err := time.Parse("2006-01-02T15:04:05", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - t, err := time.Parse(time.RFC3339, s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil -} - // GetTime returns time from the given string. func GetTime(s string) (time.Time, error) { - secs, err := parseTime(s) + secs, err := promutils.ParseTime(s) if err != nil { return time.Time{}, fmt.Errorf("cannot parse %s: %w", s, err) } @@ -101,5 +27,5 @@ func GetTime(s string) (time.Time, error) { msecs = maxTimeMsecs } - return time.Unix(0, msecs*int64(time.Millisecond)), nil + return time.Unix(0, msecs*int64(time.Millisecond)).UTC(), nil } diff --git a/app/vmselect/searchutils/searchutils.go b/app/vmselect/searchutils/searchutils.go index 4c29432c9..d2adcd97b 100644 --- a/app/vmselect/searchutils/searchutils.go +++ b/app/vmselect/searchutils/searchutils.go @@ -57,7 +57,7 @@ func GetTime(r *http.Request, argKey string, defaultMs int64) (int64, error) { return maxTimeMsecs, nil } // Parse argValue - secs, err := parseTime(argValue) + secs, err := promutils.ParseTime(argValue) if err != nil { return 0, fmt.Errorf("cannot parse %s=%s: %w", argKey, argValue, err) } @@ -71,78 +71,6 @@ func GetTime(r *http.Request, argKey string, defaultMs int64) (int64, error) { return msecs, nil } -func parseTime(s string) (float64, error) { - if len(s) > 0 && (s[len(s)-1] != 'Z' && s[len(s)-1] > '9' || s[0] == '-') { - // Parse duration relative to the current time - d, err := promutils.ParseDuration(s) - if err != nil { - return 0, err - } - if d > 0 { - d = -d - } - t := time.Now().Add(d) - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 4 { - // Parse YYYY - t, err := time.Parse("2006", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if !strings.Contains(s, "-") { - // Parse the timestamp in milliseconds - return strconv.ParseFloat(s, 64) - } - if len(s) == 7 { - // Parse YYYY-MM - t, err := time.Parse("2006-01", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 10 { - // Parse YYYY-MM-DD - t, err := time.Parse("2006-01-02", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 13 { - // Parse YYYY-MM-DDTHH - t, err := time.Parse("2006-01-02T15", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 16 { - // Parse YYYY-MM-DDTHH:MM - t, err := time.Parse("2006-01-02T15:04", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - if len(s) == 19 { - // Parse YYYY-MM-DDTHH:MM:SS - t, err := time.Parse("2006-01-02T15:04:05", s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil - } - t, err := time.Parse(time.RFC3339, s) - if err != nil { - return 0, err - } - return float64(t.UnixNano()) / 1e9, nil -} - var ( // These constants were obtained from https://github.com/prometheus/prometheus/blob/91d7175eaac18b00e370965f3a8186cc40bf9f55/web/api/v1/api.go#L442 // See https://github.com/prometheus/client_golang/issues/614 for details. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 78bce2095..7dfbca9ef 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -32,7 +32,7 @@ The following tip changes can be tested by building VictoriaMetrics components f * FEATURE: deprecate `-bigMergeConcurrency` command-line flag, since improper configuration for this flag frequently led to uncontrolled growth of unmerged parts, which, in turn, could lead to queries slowdown and increased CPU usage. The concurrency for [background merges](https://docs.victoriametrics.com/#storage) can be controlled via `-smallMergeConcurrency` command-line flag, though it isn't recommended to do in general case. * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): support new filtering options `filter` and `node_filter` for [consul service discovery](https://docs.victoriametrics.com/sd_configs.html#consul_sd_configs). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4183) for details. * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): support new [consulagent service discovery](https://docs.victoriametrics.com/sd_configs.html#consulagent_sd_configs). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3953) for details. -* FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): add support for the different time formats for `--vm-native-filter-time-start` and `--vm-native-filter-time-end` flags if the native binary protocol is used for migration. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4091). +* FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): add support for [different time formats](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#timestamp-formats) for `--vm-native-filter-time-start` and `--vm-native-filter-time-end` command-line flags. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4091). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): integrate WITH template playground. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3811). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add a comparison of data from the previous day with data from the current day to the `Cardinality Explorer`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3967). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): display histogram metrics as a heatmap in the `explore metrics` tab. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4111). diff --git a/lib/promutils/time.go b/lib/promutils/time.go new file mode 100644 index 000000000..e0490301b --- /dev/null +++ b/lib/promutils/time.go @@ -0,0 +1,84 @@ +package promutils + +import ( + "strconv" + "strings" + "time" +) + +// ParseTime parses time s in different formats. +// +// See https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#timestamp-formats +// +// It returns unix timestamp in seconds. +func ParseTime(s string) (float64, error) { + if len(s) > 0 && (s[len(s)-1] != 'Z' && s[len(s)-1] > '9' || s[0] == '-') { + // Parse duration relative to the current time + d, err := ParseDuration(s) + if err != nil { + return 0, err + } + if d > 0 { + d = -d + } + t := time.Now().Add(d) + return float64(t.UnixNano()) / 1e9, nil + } + if len(s) == 4 { + // Parse YYYY + t, err := time.Parse("2006", s) + if err != nil { + return 0, err + } + return float64(t.UnixNano()) / 1e9, nil + } + if !strings.Contains(s, "-") { + // Parse the timestamp in milliseconds + return strconv.ParseFloat(s, 64) + } + if len(s) == 7 { + // Parse YYYY-MM + t, err := time.Parse("2006-01", s) + if err != nil { + return 0, err + } + return float64(t.UnixNano()) / 1e9, nil + } + if len(s) == 10 { + // Parse YYYY-MM-DD + t, err := time.Parse("2006-01-02", s) + if err != nil { + return 0, err + } + return float64(t.UnixNano()) / 1e9, nil + } + if len(s) == 13 { + // Parse YYYY-MM-DDTHH + t, err := time.Parse("2006-01-02T15", s) + if err != nil { + return 0, err + } + return float64(t.UnixNano()) / 1e9, nil + } + if len(s) == 16 { + // Parse YYYY-MM-DDTHH:MM + t, err := time.Parse("2006-01-02T15:04", s) + if err != nil { + return 0, err + } + return float64(t.UnixNano()) / 1e9, nil + } + if len(s) == 19 { + // Parse YYYY-MM-DDTHH:MM:SS + t, err := time.Parse("2006-01-02T15:04:05", s) + if err != nil { + return 0, err + } + return float64(t.UnixNano()) / 1e9, nil + } + t, err := time.Parse(time.RFC3339, s) + if err != nil { + return 0, err + } + return float64(t.UnixNano()) / 1e9, nil +} diff --git a/lib/promutils/time_test.go b/lib/promutils/time_test.go new file mode 100644 index 000000000..e085fdc8e --- /dev/null +++ b/lib/promutils/time_test.go @@ -0,0 +1,52 @@ +package promutils + +import ( + "math" + "testing" + "time" +) + +func TestParseTimeSuccess(t *testing.T) { + f := func(s string, resultExpected float64) { + t.Helper() + result, err := ParseTime(s) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if math.Abs(result-resultExpected) > 10 { + t.Fatalf("unexpected result; got %v; want %v", result, resultExpected) + } + } + + now := float64(time.Now().UnixNano()) / 1e9 + // duration relative to the current time + f("1h5s", now-3605) + + // negative duration relative to the current time + f("-5m", now-5*60) + + // Year + f("2023", 1.6725312e+09) + + // Year and month + f("2023-05", 1.6828992e+09) + + // Year, month and day + f("2023-05-20", 1.6845408e+09) + + // Year, month, day and hour + f("2023-05-20T04", 1.6845552e+09) + + // Year, month, day, hour and minute + f("2023-05-20T04:57", 1.68455862e+09) + + // Year, month, day, hour, minute and second + f("2023-05-20T04:57:43", 1.684558663e+09) + + // RFC3339 + f("2023-05-20T04:57:43Z", 1.684558663e+09) + f("2023-05-20T04:57:43+02:30", 1.684549663e+09) + f("2023-05-20T04:57:43-02:30", 1.684567663e+09) + f("2023-05-20T04:57:43.123Z", 1.6845586631230001e+09) + f("2023-05-20T04:57:43.123456789Z", 1.6845586631230001e+09) +}