mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-01 14:47:38 +00:00
4d27933041
Use local timezone of the host server in this case. The timezone can be overridden with TZ environment variable if needed. While at it, allow using whitespace instead of T as a delimiter between data and time in the ingested _time field. For example, '2024-09-20 10:20:30' is now accepted during data ingestion. This is valid ISO8601 format, which is used by some log shippers, so it should be supported. This format is also known as SQL datetime format. Also assume local time zone when time without timezone information is passed to querying APIs. Previously such a time was parsed in UTC timezone. Add `Z` to the end of the time string if the old behaviour is preferred. Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6721
156 lines
3.8 KiB
Go
156 lines
3.8 KiB
Go
package promutils
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
|
)
|
|
|
|
// ParseTimeMsec parses time s in different formats.
|
|
//
|
|
// See https://docs.victoriametrics.com/single-server-victoriametrics/#timestamp-formats
|
|
//
|
|
// It returns unix timestamp in milliseconds.
|
|
func ParseTimeMsec(s string) (int64, error) {
|
|
currentTimestamp := time.Now().UnixNano()
|
|
nsecs, err := ParseTimeAt(s, currentTimestamp)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
msecs := int64(math.Round(float64(nsecs) / 1e6))
|
|
return msecs, nil
|
|
}
|
|
|
|
const (
|
|
// time.UnixNano can only store maxInt64, which is 2262
|
|
maxValidYear = 2262
|
|
minValidYear = 1970
|
|
)
|
|
|
|
// ParseTimeAt parses time s in different formats, assuming the given currentTimestamp.
|
|
//
|
|
// See https://docs.victoriametrics.com/single-server-victoriametrics/#timestamp-formats
|
|
//
|
|
// If s doesn't contain timezone information, then the local timezone is used.
|
|
//
|
|
// It returns unix timestamp in nanoseconds.
|
|
func ParseTimeAt(s string, currentTimestamp int64) (int64, error) {
|
|
if s == "now" {
|
|
return currentTimestamp, nil
|
|
}
|
|
sOrig := s
|
|
tzOffset := int64(0)
|
|
if len(sOrig) > 6 {
|
|
// Try parsing timezone offset
|
|
tz := sOrig[len(sOrig)-6:]
|
|
if (tz[0] == '-' || tz[0] == '+') && tz[3] == ':' {
|
|
isPlus := tz[0] == '+'
|
|
hour, err := strconv.ParseUint(tz[1:3], 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("cannot parse hour from timezone offset %q: %w", tz, err)
|
|
}
|
|
minute, err := strconv.ParseUint(tz[4:], 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("cannot parse minute from timezone offset %q: %w", tz, err)
|
|
}
|
|
tzOffset = int64(hour*3600+minute*60) * 1e9
|
|
if isPlus {
|
|
tzOffset = -tzOffset
|
|
}
|
|
s = sOrig[:len(sOrig)-6]
|
|
} else {
|
|
if !strings.HasSuffix(s, "Z") {
|
|
tzOffset = -timeutil.GetLocalTimezoneOffsetNsecs()
|
|
} else {
|
|
s = s[:len(s)-1]
|
|
}
|
|
}
|
|
}
|
|
s = strings.TrimSuffix(s, "Z")
|
|
if len(s) > 0 && (s[len(s)-1] > '9' || s[0] == '-') || strings.HasPrefix(s, "now") {
|
|
// Parse duration relative to the current time
|
|
s = strings.TrimPrefix(s, "now")
|
|
d, err := ParseDuration(s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if d > 0 {
|
|
d = -d
|
|
}
|
|
return currentTimestamp + int64(d), nil
|
|
}
|
|
if len(s) == 4 {
|
|
// Parse YYYY
|
|
t, err := time.Parse("2006", s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
y := t.Year()
|
|
if y > maxValidYear || y < minValidYear {
|
|
return 0, fmt.Errorf("cannot parse year from %q: year must in range [%d, %d]", s, minValidYear, maxValidYear)
|
|
}
|
|
return tzOffset + t.UnixNano(), nil
|
|
}
|
|
if !strings.Contains(sOrig, "-") {
|
|
// Parse the timestamp in seconds or in milliseconds
|
|
ts, err := strconv.ParseFloat(sOrig, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if ts >= (1 << 32) {
|
|
// The timestamp is in milliseconds. Convert it to seconds.
|
|
ts /= 1000
|
|
}
|
|
return int64(math.Round(ts*1e3)) * 1e6, nil
|
|
}
|
|
if len(s) == 7 {
|
|
// Parse YYYY-MM
|
|
t, err := time.Parse("2006-01", s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return tzOffset + t.UnixNano(), nil
|
|
}
|
|
if len(s) == 10 {
|
|
// Parse YYYY-MM-DD
|
|
t, err := time.Parse("2006-01-02", s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return tzOffset + t.UnixNano(), nil
|
|
}
|
|
if len(s) == 13 {
|
|
// Parse YYYY-MM-DDTHH
|
|
t, err := time.Parse("2006-01-02T15", s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return tzOffset + t.UnixNano(), 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 tzOffset + t.UnixNano(), 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 tzOffset + t.UnixNano(), nil
|
|
}
|
|
// Parse RFC3339
|
|
t, err := time.Parse(time.RFC3339, sOrig)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return t.UnixNano(), nil
|
|
}
|