From 3fee32d88160df56e291930bfaaa917ff75c170b Mon Sep 17 00:00:00 2001 From: Theodore Wahle Date: Wed, 9 Sep 2020 15:07:46 -0700 Subject: [PATCH 01/18] Added the ParseTimeReference function --- src/query/graphite/graphite/timespec.go | 162 +++++++++++++++++++ src/query/graphite/graphite/timespec_test.go | 39 +++++ 2 files changed, 201 insertions(+) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 4d64b44bd6..8a04eaaa96 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -31,6 +31,8 @@ import ( ) var reRelativeTime = regexp.MustCompile(`(?i)^\-([0-9]+)(s|min|h|d|w|mon|y)$`) +var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9]+)$`) +var reDayOfWeek = regexp.MustCompile(`(?i)^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) var periods = map[string]time.Duration{ "s": time.Second, @@ -42,6 +44,31 @@ var periods = map[string]time.Duration{ "y": time.Hour * 24 * 365, } +var weekdays = map[string]int { + "sunday" : 0, + "monday" : 1, + "tuesday" : 2, + "wednesday" : 3, + "thursday" : 4, + "friday" : 5, + "saturday" : 6, +} + +var months = map[string]int { + "january" : 1, + "february" : 2, + "march" : 3, + "april" : 4, + "may" : 5, + "june" : 6, + "july" : 7, + "august" : 8, + "september" : 9, + "october" : 10, + "november" : 11, + "december" : 12, +} + // on Jan 2 15:04:05 -0700 MST 2006 var formats = []string{ "15:04_060102", @@ -50,6 +77,7 @@ var formats = []string{ "15:04_02.01.06", "02.01.06", "01/02/06", + "01/02/2006", "060102", "20060102", } @@ -94,6 +122,7 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time return now.Add(-1 * time.Duration(timePast) * period), nil } + newS := bypassTimeParseBug(s) for _, format := range formats { t, err := time.Parse(format, newS) @@ -108,9 +137,142 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time return time.Unix(n, 0).UTC(), nil } + ref, offset := s, "" + if strings.Contains(s, "+") { + stringSlice := strings.Split(s, "+") + ref, offset = stringSlice[0], stringSlice[1] + } else if strings.Contains(s, "-") { + stringSlice := strings.Split(s, "-") + ref, offset = stringSlice[0], stringSlice[1] + } + parsedReference, err := ParseTimeReference(ref, now) + parsedOffset, err := ParseDuration(offset) + if err == nil { + return parsedReference.Add(parsedOffset), err + } + return now, err } + +func ParseTimeReference(ref string, now time.Time) (time.Time, error) { + hour := now.Hour() + minute := now.Minute() + refDate := time.Time{} + + if ref == "" || ref == "now" { + return now, nil + } + + // check if the time ref fits an absolute time format + for _, format := range formats { + t, err := time.Parse(format, ref) + if err == nil { + return time.Date(t.Year(), t.Month(), t.Day(), hour, minute, 0, 0, now.Location()), nil + } + } + + rawRef := ref + + // Time of Day Reference + i := strings.Index(ref,":") + if 0 < i && i < 3 { + newHour, err := strconv.ParseInt(ref[:i], 10, 0) + if err != nil { + return refDate, err + } + hour = int(newHour) + newMinute, err := strconv.ParseInt(ref[i+1:i+3], 10, 32) + if err != nil { + return refDate, err + } + minute = int(newMinute) + ref = ref[i+3:] + + if len(ref) >= 2 { + if ref[:2] == "am" { + ref = ref[2:] + } else if ref[:2] == "pm" { + hour = (hour + 12) % 24 + ref = ref[2:] + } + } + } + + // Xam or XXam + i = strings.Index(ref,"am") + if 0 < i && i < 3 { + newHour, err := strconv.ParseInt(ref[:i], 10, 32) + if err != nil { + return refDate, err + } + hour = int(newHour) + minute = 0 + ref = ref[i+2:] + } + + + // Xpm or XXpm + i = strings.Index(ref, "pm") + if 0 < i && i < 3 { + newHour, err := strconv.ParseInt(ref[:i], 10, 32) + if err != nil { + return refDate, err + } + hour = int((newHour + 12) % 24) + minute = 0 + ref = ref[i+2:] + } + + if strings.HasPrefix(ref, "noon") { + hour,minute = 12,0 + ref = ref[4:] + } else if strings.HasPrefix(ref, "midnight") { + hour,minute = 0,0 + ref = ref[8:] + } else if strings.HasPrefix(ref, "teatime") { + hour,minute = 16,0 + ref = ref[7:] + } + + refDate = time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location()) + + // Day reference + if ref == "yesterday" { + refDate = refDate.Add(time.Hour * -24) + } else if ref == "tomorrow" { + refDate = refDate.Add(time.Hour * 24) + } else if ref == "today" { + return refDate, nil + } else if reMonthAndDay.MatchString(ref) { // monthDay (january10, may6, may06 etc.) + day := 0 + monthString := "" + if val, err := strconv.ParseInt(ref[len(ref)-2:],10,64); err == nil { + day = int(val) + monthString = ref[:len(ref)-2] + } else if val, err := strconv.ParseInt(ref[len(ref)-1:],10,64); err == nil { + day = int(val) + monthString = ref[:len(ref)-1] + } else { + return refDate, errors.NewInvalidParamsError(fmt.Errorf("day of month required after month name")) + } + refDate = time.Date(refDate.Year(), time.Month(months[monthString]), day, hour, minute, 0, 0, refDate.Location()) + } else if reDayOfWeek.MatchString(ref) { // DayOfWeek (Monday, etc) + expectedDay := weekdays[ref] + today := int(refDate.Weekday()) + dayOffset := today - expectedDay + if dayOffset < 0 { + dayOffset += 7 + } + refDate.Add(time.Hour * 24 * -1 * time.Duration(dayOffset)) + } else if ref != "" { + return refDate, errors.NewInvalidParamsError(fmt.Errorf("unkown day reference %s", rawRef)) + } + + return refDate, nil +} + + // ParseDuration parses a duration func ParseDuration(s string) (time.Duration, error) { if m := reRelativeTime.FindStringSubmatch(s); len(m) != 0 { diff --git a/src/query/graphite/graphite/timespec_test.go b/src/query/graphite/graphite/timespec_test.go index 3f6da56f7a..5ceeca8af7 100644 --- a/src/query/graphite/graphite/timespec_test.go +++ b/src/query/graphite/graphite/timespec_test.go @@ -104,3 +104,42 @@ func TestAbsoluteOffset(t *testing.T) { assert.Equal(t, test.expectedTime, parsed, "incorrect parsed value for %s", s) } } + +// April 3 2013, 4:05 +func TestParseTimeReference(t *testing.T) { + tests := []struct { + ref string + expectedTime time.Time + }{ + {"", relativeTo}, + {"now", relativeTo}, + {"8:50", relativeTo.Add((time.Hour * 4) + (time.Minute * 45))}, + {"8:50am", relativeTo.Add((time.Hour * 4) + (time.Minute * 45))}, + {"8:50pm", relativeTo.Add((time.Hour * 16) + (time.Minute * 45))}, + {"8am", relativeTo.Add((time.Hour * 3) + (time.Minute * 55))}, + {"10pm", relativeTo.Add((time.Hour * 17) + (time.Minute * 55))}, + {"noon", relativeTo.Add((time.Hour * 7) + (time.Minute * 55))}, + {"midnight", relativeTo.Add((time.Hour * -4) + (time.Minute * -5))}, + {"teatime", relativeTo.Add((time.Hour * 12) + (time.Minute * -5))}, + {"yesterday", relativeTo.Add(time.Hour * 24 * -1)}, + {"today", relativeTo}, + {"tomorrow", relativeTo.Add(time.Hour * 24)}, + {"04/24/13", relativeTo.Add(time.Hour * 24 * 21)}, + {"04/24/2013", relativeTo.Add(time.Hour * 24 * 21)}, + {"20130424", relativeTo.Add(time.Hour * 24 * 21)}, + {"may6", relativeTo.Add(time.Hour * 24 * 33)}, + {"may06", relativeTo.Add(time.Hour * 24 * 33)}, + {"december17", relativeTo.Add(time.Hour * 24 * 258)}, + } + + for _, test := range tests { + ref := test.ref + parsed, err := ParseTimeReference(ref, relativeTo) + assert.Nil(t, err, "error parsing %s", ref) + assert.Equal(t, test.expectedTime, parsed, "incorrect parsed value for %s", ref) + } +} + +func TestParseTimeReferenceErrors(t *testing.T) { + // check errors +} \ No newline at end of file From 27f11a1e6ddca5f29e8d36000c764434a7555ab4 Mon Sep 17 00:00:00 2001 From: Theodore Wahle Date: Wed, 9 Sep 2020 21:13:10 -0700 Subject: [PATCH 02/18] Completed the Parse ATTime functionality --- src/query/graphite/graphite/timespec.go | 45 ++++++++++++---- src/query/graphite/graphite/timespec_test.go | 54 +++++++++++++++++++- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 8a04eaaa96..ba105f19b3 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -30,8 +30,9 @@ import ( "github.com/m3db/m3/src/query/graphite/errors" ) -var reRelativeTime = regexp.MustCompile(`(?i)^\-([0-9]+)(s|min|h|d|w|mon|y)$`) -var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9]+)$`) +var reRelativeTime = regexp.MustCompile(`(?i)^\-([0-9]+)(s|min|h|d|w|mon|y)$`) // allows -3min, -4d, etc. +var reTimeOffset = regexp.MustCompile(`(?i)^(\-|\+)([0-9]+)(s|min|h|d|w|mon|y)$`) // -3min, +3min, -4d, +4d +var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9][0-9])$`) var reDayOfWeek = regexp.MustCompile(`(?i)^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) var periods = map[string]time.Duration{ @@ -141,12 +142,15 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time if strings.Contains(s, "+") { stringSlice := strings.Split(s, "+") ref, offset = stringSlice[0], stringSlice[1] + offset = "+" + offset } else if strings.Contains(s, "-") { stringSlice := strings.Split(s, "-") ref, offset = stringSlice[0], stringSlice[1] + offset = "-" + offset } + err = nil parsedReference, err := ParseTimeReference(ref, now) - parsedOffset, err := ParseDuration(offset) + parsedOffset, err := ParseOffset(offset) if err == nil { return parsedReference.Add(parsedOffset), err } @@ -179,12 +183,12 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { if 0 < i && i < 3 { newHour, err := strconv.ParseInt(ref[:i], 10, 0) if err != nil { - return refDate, err + return time.Time{}, err } hour = int(newHour) newMinute, err := strconv.ParseInt(ref[i+1:i+3], 10, 32) if err != nil { - return refDate, err + return time.Time{}, err } minute = int(newMinute) ref = ref[i+3:] @@ -204,7 +208,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { if 0 < i && i < 3 { newHour, err := strconv.ParseInt(ref[:i], 10, 32) if err != nil { - return refDate, err + return time.Time{}, err } hour = int(newHour) minute = 0 @@ -217,7 +221,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { if 0 < i && i < 3 { newHour, err := strconv.ParseInt(ref[:i], 10, 32) if err != nil { - return refDate, err + return time.Time{}, err } hour = int((newHour + 12) % 24) minute = 0 @@ -264,9 +268,10 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { if dayOffset < 0 { dayOffset += 7 } - refDate.Add(time.Hour * 24 * -1 * time.Duration(dayOffset)) + offSetDuration := time.Duration(dayOffset) + refDate = refDate.Add(time.Hour * 24 * -1 * offSetDuration) } else if ref != "" { - return refDate, errors.NewInvalidParamsError(fmt.Errorf("unkown day reference %s", rawRef)) + return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unkown day reference %s", rawRef)) } return refDate, nil @@ -287,3 +292,25 @@ func ParseDuration(s string) (time.Duration, error) { return 0, errors.NewInvalidParamsError(fmt.Errorf("invalid relative time %s", s)) } + +// ParseOffset parses a time offset (like a duration, but can be 0 or positive) +func ParseOffset(s string) (time.Duration, error) { + if s == "" { + return time.Duration(0), nil + } + + if m := reTimeOffset.FindStringSubmatch(s); len(m) != 0 { + parity := 1 + if m[1] == "-" { + parity = -1 + } + timeInteger, err := strconv.ParseInt(m[2], 10, 32) + if err != nil { + return 0, errors.NewInvalidParamsError(fmt.Errorf("invalid time offset %v", err)) + } + period := periods[strings.ToLower(m[3])] + return period * time.Duration(timeInteger) * time.Duration(parity), nil + } + + return 0, errors.NewInvalidParamsError(fmt.Errorf("invalid time offset %s", s)) +} diff --git a/src/query/graphite/graphite/timespec_test.go b/src/query/graphite/graphite/timespec_test.go index 5ceeca8af7..0666cd0efa 100644 --- a/src/query/graphite/graphite/timespec_test.go +++ b/src/query/graphite/graphite/timespec_test.go @@ -46,6 +46,10 @@ func TestParseTime(t *testing.T) { {"20140307", time.Date(2014, time.March, 7, 0, 0, 0, 0, time.UTC)}, {"140307", time.Date(2014, time.March, 7, 0, 0, 0, 0, time.UTC)}, {"1432581620", time.Date(2015, time.May, 25, 19, 20, 20, 0, time.UTC)}, + {"now", time.Date(2013, time.April, 3, 4, 5, 0, 0, time.UTC)}, + {"midnight", time.Date(2013, time.April, 3, 0, 0, 0, 0, time.UTC)}, + {"midnight+1h", time.Date(2013, time.April, 3, 1, 0, 0, 0, time.UTC)}, + {"april08+1d", time.Date(2013, time.April, 9, 4, 5, 0, 0, time.UTC)}, } for _, test := range tests { @@ -74,6 +78,27 @@ func TestParseDuration(t *testing.T) { } } +func TestParseOffset(t *testing.T) { + tests := []struct { + timespec string + expectedDuration time.Duration + }{ + {"-4h", -4 * time.Hour}, + {"-35MIN", -35 * time.Minute}, + {"-10s", -10 * time.Second}, + {"+4h", 4 * time.Hour}, + {"+35MIN", 35 * time.Minute}, + {"+10s", 10 * time.Second}, + } + + for _, test := range tests { + s := test.timespec + parsed, err := ParseOffset(s) + assert.Nil(t, err, "error parsing %s", s) + assert.Equal(t, test.expectedDuration, parsed, "incorrect parsed value for %s", s) + } +} + func TestParseDurationErrors(t *testing.T) { tests := []string{ "10s", @@ -130,6 +155,7 @@ func TestParseTimeReference(t *testing.T) { {"may6", relativeTo.Add(time.Hour * 24 * 33)}, {"may06", relativeTo.Add(time.Hour * 24 * 33)}, {"december17", relativeTo.Add(time.Hour * 24 * 258)}, + {"monday", relativeTo.Add(time.Hour * 24 * -2)}, } for _, test := range tests { @@ -141,5 +167,31 @@ func TestParseTimeReference(t *testing.T) { } func TestParseTimeReferenceErrors(t *testing.T) { - // check errors + tests := []string{ + "january800", + "january", + "random", + ":", + } + + for _, test := range tests { + parsed, err := ParseTimeReference(test, relativeTo) + assert.Error(t, err) + assert.Equal(t, time.Time{}, parsed) + } +} + +func TestParseOffsetErrors(t *testing.T) { + tests := []string{ + "something", + "1m", + "10", + "month", + } + + for _, test := range tests { + parsed, err := ParseOffset(test) + assert.Error(t, err) + assert.Equal(t, time.Duration(0), parsed) + } } \ No newline at end of file From d98c5f14594e2af5a4a82cd2fce76047865a963b Mon Sep 17 00:00:00 2001 From: Theodore Wahle Date: Wed, 9 Sep 2020 21:27:28 -0700 Subject: [PATCH 03/18] Fixed broken regex --- src/query/graphite/graphite/timespec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index ba105f19b3..31cbddf025 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -32,7 +32,7 @@ import ( var reRelativeTime = regexp.MustCompile(`(?i)^\-([0-9]+)(s|min|h|d|w|mon|y)$`) // allows -3min, -4d, etc. var reTimeOffset = regexp.MustCompile(`(?i)^(\-|\+)([0-9]+)(s|min|h|d|w|mon|y)$`) // -3min, +3min, -4d, +4d -var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9][0-9])$`) +var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9]{1,2}?)$`) var reDayOfWeek = regexp.MustCompile(`(?i)^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) var periods = map[string]time.Duration{ From 894d8f2a16ce2aa1f603cc492c81e685c36f8dea Mon Sep 17 00:00:00 2001 From: teddywahle <69990143+teddywahle@users.noreply.github.com> Date: Thu, 10 Sep 2020 07:29:26 -0700 Subject: [PATCH 04/18] Update src/query/graphite/graphite/timespec.go --- src/query/graphite/graphite/timespec.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 31cbddf025..2460655580 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -123,7 +123,6 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time return now.Add(-1 * time.Duration(timePast) * period), nil } - newS := bypassTimeParseBug(s) for _, format := range formats { t, err := time.Parse(format, newS) From a8795c3717c8f6ec176065993761bb2b8369a897 Mon Sep 17 00:00:00 2001 From: teddywahle <69990143+teddywahle@users.noreply.github.com> Date: Mon, 21 Sep 2020 08:15:58 -0700 Subject: [PATCH 05/18] Update src/query/graphite/graphite/timespec.go --- src/query/graphite/graphite/timespec.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 2460655580..3ba7836b4b 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -147,12 +147,13 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time ref, offset = stringSlice[0], stringSlice[1] offset = "-" + offset } - err = nil parsedReference, err := ParseTimeReference(ref, now) - parsedOffset, err := ParseOffset(offset) - if err == nil { - return parsedReference.Add(parsedOffset), err - } +if err == nil { + parsedOffset, err := ParseOffset(offset) + if err == nil { + return parsedReference.Add(parsedOffset), nil + } +} return now, err } From c405440d2f71e614810d8d7ba805cbbd56d8cdd3 Mon Sep 17 00:00:00 2001 From: teddywahle <69990143+teddywahle@users.noreply.github.com> Date: Mon, 21 Sep 2020 08:16:36 -0700 Subject: [PATCH 06/18] Apply suggestions from code review --- src/query/graphite/graphite/timespec.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 3ba7836b4b..e9c04bc1c4 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -160,9 +160,11 @@ if err == nil { func ParseTimeReference(ref string, now time.Time) (time.Time, error) { - hour := now.Hour() - minute := now.Minute() - refDate := time.Time{} + var ( + hour = now.Hour() + minute = now.Minute() + refDate time.Time +) if ref == "" || ref == "now" { return now, nil From abaa0c01297e662c820d8a0012c4689266e69451 Mon Sep 17 00:00:00 2001 From: teddywahle <69990143+teddywahle@users.noreply.github.com> Date: Mon, 21 Sep 2020 08:17:15 -0700 Subject: [PATCH 07/18] Apply suggestions from code review --- src/query/graphite/graphite/timespec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index e9c04bc1c4..a19ee89fdb 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -273,7 +273,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { offSetDuration := time.Duration(dayOffset) refDate = refDate.Add(time.Hour * 24 * -1 * offSetDuration) } else if ref != "" { - return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unkown day reference %s", rawRef)) + return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unknown day reference %s", rawRef)) } return refDate, nil From 858d3374fff0cc43d802e1d44e40eafcfe2fcae8 Mon Sep 17 00:00:00 2001 From: teddywahle <69990143+teddywahle@users.noreply.github.com> Date: Mon, 21 Sep 2020 08:17:31 -0700 Subject: [PATCH 08/18] Apply suggestions from code review --- src/query/graphite/graphite/timespec_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/graphite/graphite/timespec_test.go b/src/query/graphite/graphite/timespec_test.go index 0666cd0efa..e881b75b3b 100644 --- a/src/query/graphite/graphite/timespec_test.go +++ b/src/query/graphite/graphite/timespec_test.go @@ -194,4 +194,4 @@ func TestParseOffsetErrors(t *testing.T) { assert.Error(t, err) assert.Equal(t, time.Duration(0), parsed) } -} \ No newline at end of file +} From 55b55c1000da78634a3fd4e5c1e675e645e3da46 Mon Sep 17 00:00:00 2001 From: Theodore Wahle Date: Mon, 21 Sep 2020 08:19:50 -0700 Subject: [PATCH 09/18] ran go fmt --- src/query/graphite/graphite/timespec.go | 85 ++++++++++---------- src/query/graphite/graphite/timespec_test.go | 2 +- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index a19ee89fdb..477d3c4784 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -30,7 +30,7 @@ import ( "github.com/m3db/m3/src/query/graphite/errors" ) -var reRelativeTime = regexp.MustCompile(`(?i)^\-([0-9]+)(s|min|h|d|w|mon|y)$`) // allows -3min, -4d, etc. +var reRelativeTime = regexp.MustCompile(`(?i)^\-([0-9]+)(s|min|h|d|w|mon|y)$`) // allows -3min, -4d, etc. var reTimeOffset = regexp.MustCompile(`(?i)^(\-|\+)([0-9]+)(s|min|h|d|w|mon|y)$`) // -3min, +3min, -4d, +4d var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9]{1,2}?)$`) var reDayOfWeek = regexp.MustCompile(`(?i)^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) @@ -45,29 +45,29 @@ var periods = map[string]time.Duration{ "y": time.Hour * 24 * 365, } -var weekdays = map[string]int { - "sunday" : 0, - "monday" : 1, - "tuesday" : 2, - "wednesday" : 3, - "thursday" : 4, - "friday" : 5, - "saturday" : 6, +var weekdays = map[string]int{ + "sunday": 0, + "monday": 1, + "tuesday": 2, + "wednesday": 3, + "thursday": 4, + "friday": 5, + "saturday": 6, } -var months = map[string]int { - "january" : 1, - "february" : 2, - "march" : 3, - "april" : 4, - "may" : 5, - "june" : 6, - "july" : 7, - "august" : 8, - "september" : 9, - "october" : 10, - "november" : 11, - "december" : 12, +var months = map[string]int{ + "january": 1, + "february": 2, + "march": 3, + "april": 4, + "may": 5, + "june": 6, + "july": 7, + "august": 8, + "september": 9, + "october": 10, + "november": 11, + "december": 12, } // on Jan 2 15:04:05 -0700 MST 2006 @@ -148,23 +148,22 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time offset = "-" + offset } parsedReference, err := ParseTimeReference(ref, now) -if err == nil { - parsedOffset, err := ParseOffset(offset) - if err == nil { - return parsedReference.Add(parsedOffset), nil - } -} + if err == nil { + parsedOffset, err := ParseOffset(offset) + if err == nil { + return parsedReference.Add(parsedOffset), nil + } + } return now, err } - func ParseTimeReference(ref string, now time.Time) (time.Time, error) { var ( - hour = now.Hour() - minute = now.Minute() - refDate time.Time -) + hour = now.Hour() + minute = now.Minute() + refDate time.Time + ) if ref == "" || ref == "now" { return now, nil @@ -181,7 +180,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { rawRef := ref // Time of Day Reference - i := strings.Index(ref,":") + i := strings.Index(ref, ":") if 0 < i && i < 3 { newHour, err := strconv.ParseInt(ref[:i], 10, 0) if err != nil { @@ -198,7 +197,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { if len(ref) >= 2 { if ref[:2] == "am" { ref = ref[2:] - } else if ref[:2] == "pm" { + } else if ref[:2] == "pm" { hour = (hour + 12) % 24 ref = ref[2:] } @@ -206,7 +205,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { } // Xam or XXam - i = strings.Index(ref,"am") + i = strings.Index(ref, "am") if 0 < i && i < 3 { newHour, err := strconv.ParseInt(ref[:i], 10, 32) if err != nil { @@ -217,7 +216,6 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { ref = ref[i+2:] } - // Xpm or XXpm i = strings.Index(ref, "pm") if 0 < i && i < 3 { @@ -231,13 +229,13 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { } if strings.HasPrefix(ref, "noon") { - hour,minute = 12,0 + hour, minute = 12, 0 ref = ref[4:] } else if strings.HasPrefix(ref, "midnight") { - hour,minute = 0,0 + hour, minute = 0, 0 ref = ref[8:] } else if strings.HasPrefix(ref, "teatime") { - hour,minute = 16,0 + hour, minute = 16, 0 ref = ref[7:] } @@ -253,17 +251,17 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { } else if reMonthAndDay.MatchString(ref) { // monthDay (january10, may6, may06 etc.) day := 0 monthString := "" - if val, err := strconv.ParseInt(ref[len(ref)-2:],10,64); err == nil { + if val, err := strconv.ParseInt(ref[len(ref)-2:], 10, 64); err == nil { day = int(val) monthString = ref[:len(ref)-2] - } else if val, err := strconv.ParseInt(ref[len(ref)-1:],10,64); err == nil { + } else if val, err := strconv.ParseInt(ref[len(ref)-1:], 10, 64); err == nil { day = int(val) monthString = ref[:len(ref)-1] } else { return refDate, errors.NewInvalidParamsError(fmt.Errorf("day of month required after month name")) } refDate = time.Date(refDate.Year(), time.Month(months[monthString]), day, hour, minute, 0, 0, refDate.Location()) - } else if reDayOfWeek.MatchString(ref) { // DayOfWeek (Monday, etc) + } else if reDayOfWeek.MatchString(ref) { // DayOfWeek (Monday, etc) expectedDay := weekdays[ref] today := int(refDate.Weekday()) dayOffset := today - expectedDay @@ -279,7 +277,6 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { return refDate, nil } - // ParseDuration parses a duration func ParseDuration(s string) (time.Duration, error) { if m := reRelativeTime.FindStringSubmatch(s); len(m) != 0 { diff --git a/src/query/graphite/graphite/timespec_test.go b/src/query/graphite/graphite/timespec_test.go index e881b75b3b..7ea82ef7d0 100644 --- a/src/query/graphite/graphite/timespec_test.go +++ b/src/query/graphite/graphite/timespec_test.go @@ -133,7 +133,7 @@ func TestAbsoluteOffset(t *testing.T) { // April 3 2013, 4:05 func TestParseTimeReference(t *testing.T) { tests := []struct { - ref string + ref string expectedTime time.Time }{ {"", relativeTo}, From 6f007ed2bedd7fe88dcb402d9b3da02c798cec21 Mon Sep 17 00:00:00 2001 From: Theodore Wahle Date: Mon, 21 Sep 2020 08:30:45 -0700 Subject: [PATCH 10/18] Added bounds check --- src/query/graphite/graphite/timespec.go | 12 +++++++----- src/query/graphite/graphite/timespec_test.go | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 477d3c4784..31b46e4009 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -187,12 +187,14 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { return time.Time{}, err } hour = int(newHour) - newMinute, err := strconv.ParseInt(ref[i+1:i+3], 10, 32) - if err != nil { - return time.Time{}, err + if len(ref) >= i+3 { + newMinute, err := strconv.ParseInt(ref[i+1:i+3], 10, 32) + if err != nil { + return time.Time{}, err + } + minute = int(newMinute) + ref = ref[i+3:] } - minute = int(newMinute) - ref = ref[i+3:] if len(ref) >= 2 { if ref[:2] == "am" { diff --git a/src/query/graphite/graphite/timespec_test.go b/src/query/graphite/graphite/timespec_test.go index 7ea82ef7d0..616226ae20 100644 --- a/src/query/graphite/graphite/timespec_test.go +++ b/src/query/graphite/graphite/timespec_test.go @@ -172,6 +172,7 @@ func TestParseTimeReferenceErrors(t *testing.T) { "january", "random", ":", + "8:5", } for _, test := range tests { From a871c89dabc49f9e219c61c8caf0a875baa421c5 Mon Sep 17 00:00:00 2001 From: teddywahle <69990143+teddywahle@users.noreply.github.com> Date: Wed, 23 Sep 2020 13:51:53 -0700 Subject: [PATCH 11/18] Update src/query/graphite/graphite/timespec_test.go From c5204190ca06f6a88abee7dff7712578edc62f6d Mon Sep 17 00:00:00 2001 From: teddywahle <69990143+teddywahle@users.noreply.github.com> Date: Wed, 30 Sep 2020 20:28:27 -0700 Subject: [PATCH 12/18] Apply suggestions from code review --- src/query/graphite/graphite/timespec.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 31b46e4009..b905a2f66d 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -140,12 +140,16 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time ref, offset := s, "" if strings.Contains(s, "+") { stringSlice := strings.Split(s, "+") - ref, offset = stringSlice[0], stringSlice[1] - offset = "+" + offset + if len(stringSlice) == 2 { + ref, offset = stringSlice[0], stringSlice[1] + offset = "+" + offset + } } else if strings.Contains(s, "-") { stringSlice := strings.Split(s, "-") - ref, offset = stringSlice[0], stringSlice[1] - offset = "-" + offset + if len(stringSlice) == 2 { + ref, offset = stringSlice[0], stringSlice[1] + offset = "-" + offset + } } parsedReference, err := ParseTimeReference(ref, now) if err == nil { From 5a49e80da668c796029494a1cef73c56876e266c Mon Sep 17 00:00:00 2001 From: teddywahle <69990143+teddywahle@users.noreply.github.com> Date: Wed, 30 Sep 2020 20:30:27 -0700 Subject: [PATCH 13/18] Apply suggestions from code review --- src/query/graphite/graphite/timespec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index b905a2f66d..1c785be7f5 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -177,7 +177,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { for _, format := range formats { t, err := time.Parse(format, ref) if err == nil { - return time.Date(t.Year(), t.Month(), t.Day(), hour, minute, 0, 0, now.Location()), nil + return t, nil } } From 6d16b08b2507db7f8be89018ebcd2ac48e281abd Mon Sep 17 00:00:00 2001 From: teddywahle Date: Wed, 30 Sep 2020 21:41:13 -0700 Subject: [PATCH 14/18] Added some (not all) of necessary changes to parse time --- src/query/graphite/graphite/timespec.go | 44 +++++++++++++------- src/query/graphite/graphite/timespec_test.go | 14 ++++--- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 1c785be7f5..81329f2d79 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -34,6 +34,8 @@ var reRelativeTime = regexp.MustCompile(`(?i)^\-([0-9]+)(s|min|h|d|w|mon|y)$`) var reTimeOffset = regexp.MustCompile(`(?i)^(\-|\+)([0-9]+)(s|min|h|d|w|mon|y)$`) // -3min, +3min, -4d, +4d var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9]{1,2}?)$`) var reDayOfWeek = regexp.MustCompile(`(?i)^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) +var reDayOfWeekOffset = regexp.MustCompile(`(?i)^(\-|\+)(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) // +monday, +thursday, etc +var reDayOfWeekOffset = regexp.MustCompile(`(?i)^(\-|\+)(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) // +monday, +thursday, etc var periods = map[string]time.Duration{ "s": time.Second, @@ -103,6 +105,18 @@ func FormatTime(t time.Time) string { return t.Format(formats[0]) } +func getWeekDayOffset(weekday string, now time.Time) time.Duration { + expectedDay := weekdays[weekday] + today := int(now.Weekday()) + dayOffset := today - expectedDay + if dayOffset < 0 { + dayOffset += 7 + } + + return time.Duration(dayOffset) * time.Hour * -24 +} + + // ParseTime translates a Graphite from/until string into a timestamp relative to the provide time func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time, error) { if len(s) == 0 { @@ -141,19 +155,19 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time if strings.Contains(s, "+") { stringSlice := strings.Split(s, "+") if len(stringSlice) == 2 { - ref, offset = stringSlice[0], stringSlice[1] - offset = "+" + offset + ref, offset = stringSlice[0], stringSlice[1] + offset = "+" + offset } } else if strings.Contains(s, "-") { stringSlice := strings.Split(s, "-") if len(stringSlice) == 2 { - ref, offset = stringSlice[0], stringSlice[1] - offset = "-" + offset + ref, offset = stringSlice[0], stringSlice[1] + offset = "-" + offset } } parsedReference, err := ParseTimeReference(ref, now) if err == nil { - parsedOffset, err := ParseOffset(offset) + parsedOffset, err := ParseOffset(offset, now) if err == nil { return parsedReference.Add(parsedOffset), nil } @@ -162,6 +176,7 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time return now, err } +// ParseTimeReference takes a Graphite time reference ("8am", "today", "monday") and returns a time.Time func ParseTimeReference(ref string, now time.Time) (time.Time, error) { var ( hour = now.Hour() @@ -229,6 +244,9 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { if err != nil { return time.Time{}, err } + if newHour > 24 { + return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unknown time reference %s", rawRef)) + } hour = int((newHour + 12) % 24) minute = 0 ref = ref[i+2:] @@ -268,16 +286,9 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { } refDate = time.Date(refDate.Year(), time.Month(months[monthString]), day, hour, minute, 0, 0, refDate.Location()) } else if reDayOfWeek.MatchString(ref) { // DayOfWeek (Monday, etc) - expectedDay := weekdays[ref] - today := int(refDate.Weekday()) - dayOffset := today - expectedDay - if dayOffset < 0 { - dayOffset += 7 - } - offSetDuration := time.Duration(dayOffset) - refDate = refDate.Add(time.Hour * 24 * -1 * offSetDuration) + refDate = refDate.Add(getWeekDayOffset(ref, refDate)) } else if ref != "" { - return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unknown day reference %s", rawRef)) + return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unknown time reference %s", rawRef)) } return refDate, nil @@ -299,7 +310,7 @@ func ParseDuration(s string) (time.Duration, error) { } // ParseOffset parses a time offset (like a duration, but can be 0 or positive) -func ParseOffset(s string) (time.Duration, error) { +func ParseOffset(s string, now time.Time) (time.Duration, error) { if s == "" { return time.Duration(0), nil } @@ -315,6 +326,9 @@ func ParseOffset(s string) (time.Duration, error) { } period := periods[strings.ToLower(m[3])] return period * time.Duration(timeInteger) * time.Duration(parity), nil + } else if m := reDayOfWeekOffset.FindStringSubmatch(s); len(m) != 0 { + nameOfWeekday := s[1:] + return getWeekDayOffset(nameOfWeekday, now), nil } return 0, errors.NewInvalidParamsError(fmt.Errorf("invalid time offset %s", s)) diff --git a/src/query/graphite/graphite/timespec_test.go b/src/query/graphite/graphite/timespec_test.go index 616226ae20..a2eb5d1f88 100644 --- a/src/query/graphite/graphite/timespec_test.go +++ b/src/query/graphite/graphite/timespec_test.go @@ -50,6 +50,8 @@ func TestParseTime(t *testing.T) { {"midnight", time.Date(2013, time.April, 3, 0, 0, 0, 0, time.UTC)}, {"midnight+1h", time.Date(2013, time.April, 3, 1, 0, 0, 0, time.UTC)}, {"april08+1d", time.Date(2013, time.April, 9, 4, 5, 0, 0, time.UTC)}, + {"monday", relativeTo.Add(time.Hour * 24 * -2)}, + {"9am+monday", relativeTo.Add((time.Hour * 24 * -2) + (time.Hour * 4) + (time.Minute * 55))}, } for _, test := range tests { @@ -93,7 +95,7 @@ func TestParseOffset(t *testing.T) { for _, test := range tests { s := test.timespec - parsed, err := ParseOffset(s) + parsed, err := ParseOffset(s, relativeTo) assert.Nil(t, err, "error parsing %s", s) assert.Equal(t, test.expectedDuration, parsed, "incorrect parsed value for %s", s) } @@ -149,9 +151,9 @@ func TestParseTimeReference(t *testing.T) { {"yesterday", relativeTo.Add(time.Hour * 24 * -1)}, {"today", relativeTo}, {"tomorrow", relativeTo.Add(time.Hour * 24)}, - {"04/24/13", relativeTo.Add(time.Hour * 24 * 21)}, - {"04/24/2013", relativeTo.Add(time.Hour * 24 * 21)}, - {"20130424", relativeTo.Add(time.Hour * 24 * 21)}, + {"04/24/13", relativeTo.Add((time.Hour * 24 * 21) + (time.Hour * -4) + (time.Minute * -5))}, + {"04/24/2013", relativeTo.Add((time.Hour * 24 * 21) + (time.Hour * -4) + (time.Minute * -5))}, + {"20130424", relativeTo.Add((time.Hour * 24 * 21) + (time.Hour * -4) + (time.Minute * -5))}, {"may6", relativeTo.Add(time.Hour * 24 * 33)}, {"may06", relativeTo.Add(time.Hour * 24 * 33)}, {"december17", relativeTo.Add(time.Hour * 24 * 258)}, @@ -173,6 +175,8 @@ func TestParseTimeReferenceErrors(t *testing.T) { "random", ":", "8:5", + "10:00pm6am", + "99pm", } for _, test := range tests { @@ -191,7 +195,7 @@ func TestParseOffsetErrors(t *testing.T) { } for _, test := range tests { - parsed, err := ParseOffset(test) + parsed, err := ParseOffset(test, relativeTo) assert.Error(t, err) assert.Equal(t, time.Duration(0), parsed) } From eb6eaf7ebb1f96cdd6ec6ef72b74915117b906ac Mon Sep 17 00:00:00 2001 From: teddywahle Date: Thu, 1 Oct 2020 09:45:16 -0700 Subject: [PATCH 15/18] Added more crons and error checks --- src/query/graphite/graphite/timespec.go | 38 +++++++++++--------- src/query/graphite/graphite/timespec_test.go | 4 ++- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 81329f2d79..3f6a46230f 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -35,7 +35,9 @@ var reTimeOffset = regexp.MustCompile(`(?i)^(\-|\+)([0-9]+)(s|min|h|d|w|mon|y)$` var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9]{1,2}?)$`) var reDayOfWeek = regexp.MustCompile(`(?i)^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) var reDayOfWeekOffset = regexp.MustCompile(`(?i)^(\-|\+)(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) // +monday, +thursday, etc -var reDayOfWeekOffset = regexp.MustCompile(`(?i)^(\-|\+)(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) // +monday, +thursday, etc +var rePM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])pm)$`) // 8pm, 12pm +var reAM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])am)$`) // 2am, 11am +var reTimeOfDayWithColon = regexp.MustCompile(`(?i)^(([0-1]?)([0-9]):([0-5])([0-9])((am|pm)?))$`) // 8:12pm, 11:20am, 2:00am var periods = map[string]time.Duration{ "s": time.Second, @@ -184,6 +186,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { refDate time.Time ) + ref = strings.ToLower(ref) if ref == "" || ref == "now" { return now, nil } @@ -198,9 +201,9 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { rawRef := ref - // Time of Day Reference - i := strings.Index(ref, ":") - if 0 < i && i < 3 { + // Time of Day Reference (8:12pm, 11:20am, 2:00am, etc.) + if reTimeOfDayWithColon.MatchString(rawRef) { + i := strings.Index(ref, ":") newHour, err := strconv.ParseInt(ref[:i], 10, 0) if err != nil { return time.Time{}, err @@ -212,6 +215,9 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { return time.Time{}, err } minute = int(newMinute) + if minute > 59 { + return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unknown time reference %s", rawRef)) + } ref = ref[i+3:] } @@ -226,8 +232,8 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { } // Xam or XXam - i = strings.Index(ref, "am") - if 0 < i && i < 3 { + if reAM.MatchString(rawRef) { + i := strings.Index(ref, "am") newHour, err := strconv.ParseInt(ref[:i], 10, 32) if err != nil { return time.Time{}, err @@ -238,8 +244,8 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { } // Xpm or XXpm - i = strings.Index(ref, "pm") - if 0 < i && i < 3 { + if rePM.MatchString(rawRef) { + i := strings.Index(ref, "pm") newHour, err := strconv.ParseInt(ref[:i], 10, 32) if err != nil { return time.Time{}, err @@ -252,13 +258,13 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { ref = ref[i+2:] } - if strings.HasPrefix(ref, "noon") { + if strings.HasPrefix(rawRef, "noon") { hour, minute = 12, 0 ref = ref[4:] - } else if strings.HasPrefix(ref, "midnight") { + } else if strings.HasPrefix(rawRef, "midnight") { hour, minute = 0, 0 ref = ref[8:] - } else if strings.HasPrefix(ref, "teatime") { + } else if strings.HasPrefix(rawRef, "teatime") { hour, minute = 16, 0 ref = ref[7:] } @@ -266,13 +272,13 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { refDate = time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location()) // Day reference - if ref == "yesterday" { + if rawRef == "yesterday" { refDate = refDate.Add(time.Hour * -24) - } else if ref == "tomorrow" { + } else if rawRef == "tomorrow" { refDate = refDate.Add(time.Hour * 24) - } else if ref == "today" { + } else if rawRef == "today" { return refDate, nil - } else if reMonthAndDay.MatchString(ref) { // monthDay (january10, may6, may06 etc.) + } else if reMonthAndDay.MatchString(rawRef) { // monthDay (january10, may6, may06 etc.) day := 0 monthString := "" if val, err := strconv.ParseInt(ref[len(ref)-2:], 10, 64); err == nil { @@ -285,7 +291,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { return refDate, errors.NewInvalidParamsError(fmt.Errorf("day of month required after month name")) } refDate = time.Date(refDate.Year(), time.Month(months[monthString]), day, hour, minute, 0, 0, refDate.Location()) - } else if reDayOfWeek.MatchString(ref) { // DayOfWeek (Monday, etc) + } else if reDayOfWeek.MatchString(rawRef) { // DayOfWeek (Monday, etc) refDate = refDate.Add(getWeekDayOffset(ref, refDate)) } else if ref != "" { return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unknown time reference %s", rawRef)) diff --git a/src/query/graphite/graphite/timespec_test.go b/src/query/graphite/graphite/timespec_test.go index a2eb5d1f88..95e2824652 100644 --- a/src/query/graphite/graphite/timespec_test.go +++ b/src/query/graphite/graphite/timespec_test.go @@ -175,8 +175,10 @@ func TestParseTimeReferenceErrors(t *testing.T) { "random", ":", "8:5", - "10:00pm6am", "99pm", + "12:77pm", + "23:00pm", + "10:00pm6am", } for _, test := range tests { From caf1795d6c958e10051d318a7f5d29db47edc6b1 Mon Sep 17 00:00:00 2001 From: teddywahle Date: Thu, 1 Oct 2020 09:56:22 -0700 Subject: [PATCH 16/18] ran go fmt --- src/query/graphite/graphite/timespec.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 3f6a46230f..5ba943ba17 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -35,9 +35,9 @@ var reTimeOffset = regexp.MustCompile(`(?i)^(\-|\+)([0-9]+)(s|min|h|d|w|mon|y)$` var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9]{1,2}?)$`) var reDayOfWeek = regexp.MustCompile(`(?i)^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) var reDayOfWeekOffset = regexp.MustCompile(`(?i)^(\-|\+)(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) // +monday, +thursday, etc -var rePM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])pm)$`) // 8pm, 12pm -var reAM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])am)$`) // 2am, 11am -var reTimeOfDayWithColon = regexp.MustCompile(`(?i)^(([0-1]?)([0-9]):([0-5])([0-9])((am|pm)?))$`) // 8:12pm, 11:20am, 2:00am +var rePM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])pm)$`) // 8pm, 12pm +var reAM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])am)$`) // 2am, 11am +var reTimeOfDayWithColon = regexp.MustCompile(`(?i)^(([0-1]?)([0-9]):([0-5])([0-9])((am|pm)?))$`) // 8:12pm, 11:20am, 2:00am var periods = map[string]time.Duration{ "s": time.Second, @@ -118,7 +118,6 @@ func getWeekDayOffset(weekday string, now time.Time) time.Duration { return time.Duration(dayOffset) * time.Hour * -24 } - // ParseTime translates a Graphite from/until string into a timestamp relative to the provide time func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time, error) { if len(s) == 0 { From 800b95e2d8521121f195feba78222b20d45b6f05 Mon Sep 17 00:00:00 2001 From: teddywahle Date: Thu, 1 Oct 2020 21:22:50 -0700 Subject: [PATCH 17/18] fixed parse time --- src/query/graphite/graphite/timespec.go | 27 +++++++++----------- src/query/graphite/graphite/timespec_test.go | 9 +++++-- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 5ba943ba17..0fb00540a9 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -35,9 +35,9 @@ var reTimeOffset = regexp.MustCompile(`(?i)^(\-|\+)([0-9]+)(s|min|h|d|w|mon|y)$` var reMonthAndDay = regexp.MustCompile(`(?i)^(january|february|march|april|may|june|july|august|september|october|november|december)([0-9]{1,2}?)$`) var reDayOfWeek = regexp.MustCompile(`(?i)^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) var reDayOfWeekOffset = regexp.MustCompile(`(?i)^(\-|\+)(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$`) // +monday, +thursday, etc -var rePM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])pm)$`) // 8pm, 12pm -var reAM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])am)$`) // 2am, 11am -var reTimeOfDayWithColon = regexp.MustCompile(`(?i)^(([0-1]?)([0-9]):([0-5])([0-9])((am|pm)?))$`) // 8:12pm, 11:20am, 2:00am +var rePM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])pm)([[:ascii:]])*$`) // 8pm, 12pm, 8pm monday +var reAM = regexp.MustCompile(`(?i)^(([0-1]?)([0-9])am)([[:ascii:]])*$`) // 2am, 11am, 7am yesterday +var reTimeOfDayWithColon = regexp.MustCompile(`(?i)^(([0-1]?)([0-9]):([0-5])([0-9])((am|pm)?))([[:ascii:]])*$`) // 8:12pm, 11:20am, 2:00am var periods = map[string]time.Duration{ "s": time.Second, @@ -152,6 +152,7 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time return time.Unix(n, 0).UTC(), nil } + s = strings.Replace(strings.Replace(strings.ToLower(s), ",", "", -1), " ", "", -1) ref, offset := s, "" if strings.Contains(s, "+") { stringSlice := strings.Split(s, "+") @@ -185,7 +186,6 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { refDate time.Time ) - ref = strings.ToLower(ref) if ref == "" || ref == "now" { return now, nil } @@ -257,13 +257,13 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { ref = ref[i+2:] } - if strings.HasPrefix(rawRef, "noon") { + if strings.HasPrefix(ref, "noon") { hour, minute = 12, 0 ref = ref[4:] - } else if strings.HasPrefix(rawRef, "midnight") { + } else if strings.HasPrefix(ref, "midnight") { hour, minute = 0, 0 ref = ref[8:] - } else if strings.HasPrefix(rawRef, "teatime") { + } else if strings.HasPrefix(ref, "teatime") { hour, minute = 16, 0 ref = ref[7:] } @@ -271,13 +271,13 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { refDate = time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location()) // Day reference - if rawRef == "yesterday" { + if ref == "yesterday" { refDate = refDate.Add(time.Hour * -24) - } else if rawRef == "tomorrow" { + } else if ref == "tomorrow" { refDate = refDate.Add(time.Hour * 24) - } else if rawRef == "today" { + } else if ref == "today" { return refDate, nil - } else if reMonthAndDay.MatchString(rawRef) { // monthDay (january10, may6, may06 etc.) + } else if reMonthAndDay.MatchString(ref) { // monthDay (january10, may6, may06 etc.) day := 0 monthString := "" if val, err := strconv.ParseInt(ref[len(ref)-2:], 10, 64); err == nil { @@ -290,7 +290,7 @@ func ParseTimeReference(ref string, now time.Time) (time.Time, error) { return refDate, errors.NewInvalidParamsError(fmt.Errorf("day of month required after month name")) } refDate = time.Date(refDate.Year(), time.Month(months[monthString]), day, hour, minute, 0, 0, refDate.Location()) - } else if reDayOfWeek.MatchString(rawRef) { // DayOfWeek (Monday, etc) + } else if reDayOfWeek.MatchString(ref) { // DayOfWeek (Monday, etc) refDate = refDate.Add(getWeekDayOffset(ref, refDate)) } else if ref != "" { return time.Time{}, errors.NewInvalidParamsError(fmt.Errorf("unknown time reference %s", rawRef)) @@ -331,9 +331,6 @@ func ParseOffset(s string, now time.Time) (time.Duration, error) { } period := periods[strings.ToLower(m[3])] return period * time.Duration(timeInteger) * time.Duration(parity), nil - } else if m := reDayOfWeekOffset.FindStringSubmatch(s); len(m) != 0 { - nameOfWeekday := s[1:] - return getWeekDayOffset(nameOfWeekday, now), nil } return 0, errors.NewInvalidParamsError(fmt.Errorf("invalid time offset %s", s)) diff --git a/src/query/graphite/graphite/timespec_test.go b/src/query/graphite/graphite/timespec_test.go index 95e2824652..c394fb6d3b 100644 --- a/src/query/graphite/graphite/timespec_test.go +++ b/src/query/graphite/graphite/timespec_test.go @@ -50,8 +50,10 @@ func TestParseTime(t *testing.T) { {"midnight", time.Date(2013, time.April, 3, 0, 0, 0, 0, time.UTC)}, {"midnight+1h", time.Date(2013, time.April, 3, 1, 0, 0, 0, time.UTC)}, {"april08+1d", time.Date(2013, time.April, 9, 4, 5, 0, 0, time.UTC)}, - {"monday", relativeTo.Add(time.Hour * 24 * -2)}, - {"9am+monday", relativeTo.Add((time.Hour * 24 * -2) + (time.Hour * 4) + (time.Minute * 55))}, + {"monday", time.Date(2013, time.April, 1, 4, 5, 0, 0, time.UTC)}, + {"9am monday", time.Date(2013, time.April, 1, 9, 0, 0, 0, time.UTC)}, + {"9am monday +5min", time.Date(2013, time.April, 1, 9, 5, 0, 0, time.UTC)}, + {"9:00am monday +5min", time.Date(2013, time.April, 1, 9, 5, 0, 0, time.UTC)}, } for _, test := range tests { @@ -158,6 +160,9 @@ func TestParseTimeReference(t *testing.T) { {"may06", relativeTo.Add(time.Hour * 24 * 33)}, {"december17", relativeTo.Add(time.Hour * 24 * 258)}, {"monday", relativeTo.Add(time.Hour * 24 * -2)}, + // strings have whitespace removed before being passed into ParseTimeReference + {"8ammonday", relativeTo.Add((time.Hour * 24 * -2) + (time.Hour * 3) + (time.Minute * 55))}, + {"10pmyesterday", relativeTo.Add((time.Hour * 17) + (time.Minute * 55) + (time.Hour * 24 * -1))}, } for _, test := range tests { From 91cbaeb00a2d15adc334d5fb21ea4775b89a3bd8 Mon Sep 17 00:00:00 2001 From: teddywahle Date: Mon, 5 Oct 2020 11:52:06 -0700 Subject: [PATCH 18/18] added nit changes from code review --- src/query/graphite/graphite/timespec.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/query/graphite/graphite/timespec.go b/src/query/graphite/graphite/timespec.go index 0fb00540a9..a629a8929f 100644 --- a/src/query/graphite/graphite/timespec.go +++ b/src/query/graphite/graphite/timespec.go @@ -159,12 +159,16 @@ func ParseTime(s string, now time.Time, absoluteOffset time.Duration) (time.Time if len(stringSlice) == 2 { ref, offset = stringSlice[0], stringSlice[1] offset = "+" + offset + } else { + return now, errors.NewInvalidParamsError(fmt.Errorf("unknown time string %s", s)) } } else if strings.Contains(s, "-") { stringSlice := strings.Split(s, "-") if len(stringSlice) == 2 { ref, offset = stringSlice[0], stringSlice[1] offset = "-" + offset + } else { + return now, errors.NewInvalidParamsError(fmt.Errorf("unknown time string %s", s)) } } parsedReference, err := ParseTimeReference(ref, now)