VictoriaMetrics/lib/promutils/time.go

157 lines
3.8 KiB
Go
Raw Normal View History

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) {
2024-06-03 22:59:25 +00:00
currentTimestamp := time.Now().UnixNano()
nsecs, err := ParseTimeAt(s, currentTimestamp)
if err != nil {
return 0, err
}
2024-06-03 22:59:25 +00:00
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.
//
2024-06-03 22:59:25 +00:00
// It returns unix timestamp in nanoseconds.
func ParseTimeAt(s string, currentTimestamp int64) (int64, error) {
if s == "now" {
return currentTimestamp, nil
}
sOrig := s
2024-06-03 22:59:25 +00:00
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)
}
2024-06-03 22:59:25 +00:00
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
}
2024-06-03 22:59:25 +00:00
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)
}
2024-06-03 22:59:25 +00:00
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
}
2024-06-03 22:59:25 +00:00
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
}
2024-06-03 22:59:25 +00:00
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
}
2024-06-03 22:59:25 +00:00
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
}
2024-06-03 22:59:25 +00:00
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
}
2024-06-03 22:59:25 +00:00
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
}
2024-06-03 22:59:25 +00:00
return tzOffset + t.UnixNano(), nil
}
// Parse RFC3339
t, err := time.Parse(time.RFC3339, sOrig)
if err != nil {
return 0, err
}
2024-06-03 22:59:25 +00:00
return t.UnixNano(), nil
}