Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve handling of time parsing #52

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/google/flatbuffers v2.0.6+incompatible // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/handlers v1.5.1
github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d
github.com/lib/pq v1.10.6
github.com/lomik/og-rek v0.0.0-20170411191824-628eefeb8d80
github.com/lomik/zapwriter v0.0.0-20210624082824-c1161d1eb463
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d h1:2puqoOQwi3Ai1oznMOsFIbifm6kIfJaLLyYzWD4IzTs=
github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d/go.mod h1:hO90vCP2x3exaSH58BIAowSKvV+0OsY21TtzuFGHON4=
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lomik/og-rek v0.0.0-20170411191824-628eefeb8d80 h1:KVyDGUXjVOdHQt24wIgY4ZdGFXHtQHLWw0L/MAK3Kb0=
Expand Down
244 changes: 244 additions & 0 deletions pkg/parser/time_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package parser

import (
"fmt"
"github.com/leekchan/timeutil"
"strconv"
"strings"
"time"
)

var months = []string{"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}
var weekdays = []string{"sun", "mon", "tue", "wed", "thu", "fri", "sat"}

func ParseDateTime(dateTime string, defaultSign int) (int64, error) {
var parsed []string
var offset string
var ref string
r := strings.NewReplacer(
" ", "",
",", "",
"_", "",
)
parsedTime := strings.TrimSpace(dateTime)
parsedTime = r.Replace(parsedTime)

val, err := strconv.Atoi(parsedTime)
if err == nil {
year, _ := strconv.Atoi(parsedTime[:4])
month, _ := strconv.Atoi(parsedTime[4:6])
day, _ := strconv.Atoi(parsedTime[6:])
if len(parsedTime) == 8 && year > 1900 && month < 13 && day < 32 {
if strings.Contains(parsedTime, "-") {
parsed = strings.SplitN(parsedTime, "-", 2)
offset = "-" + parsed[1]
ref = parsed[0]
} else if strings.Contains(parsedTime, "+") {
parsed = strings.SplitN(parsedTime, "+", 2)
offset = "+" + parsed[1]
ref = parsed[0]
} else {
offset = ""
ref = parsedTime
}

refTime, _ := parseTimeReference(ref)
interval, _ := parseInterval(offset, 1)

total := refTime + interval
return total, nil
} else {
return int64(val), nil
}
}

return 0, err
}

func parseTimeReference(ref string) (int64, error) {
if ref == "" {
return 0, nil
}
var rawRef = ref
var err error
var refDate time.Time

refDate, _ = getReferenceDate(ref)

// Day reference
if strings.Contains(ref, "today") || strings.Contains(ref, "yesterday") || strings.Contains(ref, "tomorrow") {
if strings.Contains(ref, "yesterday") {
refDate = refDate.AddDate(0, 0, -1)
} else if strings.Contains(ref, "tomorrow") {
refDate = time.Now().AddDate(0, 0, 1)
}
} else if strings.Count(ref, "/") == 2 { // MM/DD/YY format
fmt.Println("Ref: ", ref)
refDate, err = time.Parse("2006/01/02", ref)
if err != nil {
return 0, err
}
} else if _, err := strconv.Atoi(ref); err == nil && len(ref) == 8 { // YYYYMMDD format
refDate, err = time.Parse("20060102", ref)
if err != nil {
return 0, err
}
} else if len(ref) >= 3 && stringMatchesList(ref[:3], months) { // MonthName DayOfMonth
var day int
if val, err := strconv.Atoi(ref[:2]); err == nil {
day = val
} else if val, err := strconv.Atoi(ref[:1]); err == nil {
day = val
} else {
return 0, fmt.Errorf("Day must be included in date: %s", rawRef)
}
refDate = refDate.AddDate(0, 0, day)
} else if len(ref) >= 3 && stringMatchesList(ref[:3], weekdays) { // DayOfWeek (Monday, etc)
dayName := timeutil.Strftime(&refDate, "%a")
dayName = strings.ToLower(dayName)
today := stringMatchesListIndex(dayName, weekdays)
twoWeeks := append(weekdays, weekdays...)
dayOffset := today - stringMatchesListIndex(ref[:3], twoWeeks)
if dayOffset < 0 {
dayOffset += 7
}
refDate = refDate.AddDate(0, 0, -(dayOffset))
} else if ref == "" {
return 0, fmt.Errorf("Unknown day reference: %s", rawRef)
}

return refDate.Unix(), nil
}

// IntervalString converts a sign and string into a number of seconds
func parseInterval(s string, defaultSign int) (int64, error) {
if len(s) == 0 {
return 0, nil
}
sign := defaultSign

switch s[0] {
case '-':
sign = -1
s = s[1:]
case '+':
sign = 1
s = s[1:]
}

var totalInterval int64
for len(s) > 0 {
var j int
for j < len(s) && '0' <= s[j] && s[j] <= '9' {
j++
}
var offsetStr string
offsetStr, s = s[:j], s[j:]

j = 0
for j < len(s) && (s[j] < '0' || '9' < s[j]) {
j++
}
var unitStr string
unitStr, s = s[:j], s[j:]

var units int
switch unitStr {
case "s", "sec", "secs", "second", "seconds":
units = 1
case "m", "min", "mins", "minute", "minutes":
units = 60
case "h", "hour", "hours":
units = 60 * 60
case "d", "day", "days":
units = 24 * 60 * 60
case "w", "week", "weeks":
units = 7 * 24 * 60 * 60
case "mon", "month", "months":
units = 30 * 24 * 60 * 60
case "y", "year", "years":
units = 365 * 24 * 60 * 60
default:
return 0, ErrUnknownTimeUnits
}

offset, err := strconv.Atoi(offsetStr)
if err != nil {
return 0, err
}
totalInterval += int64(sign * offset * units)
}

return totalInterval, nil
}

func stringMatchesList(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

func getReferenceDate(ref string) (time.Time, string) {
// Time-of-day reference
var hour = 0
var minute = 0
i := strings.Index(ref, ":")
if i > 0 && i < 3 {
hour, _ = strconv.Atoi(ref[:i])
minute, _ = strconv.Atoi(ref[i+1 : i+3])
ref = ref[i+3:]
if ref[:2] == "am" {
ref = ref[2:]
} else if ref[:2] == "pm" {
hour = (hour + 12) % 24
ref = ref[2:]
}
}

// X am or XXam
i = strings.Index(ref, "am")
if i > 0 && i < 3 {
hour, _ = strconv.Atoi(ref[:i])
ref = ref[i+2:]
}

// X pm or XX pm
i = strings.Index(ref, "pm")
if i > 0 && i < 3 {
hr, _ := strconv.Atoi(ref[:i])
hour = (hr + 12) % 24
ref = ref[i+2:]
}

if strings.HasPrefix(ref, "noon") {
hour = 12
minute = 0
ref = ref[4:]
} else if strings.HasPrefix(ref, "midnight") {
hour = 0
minute = 0
ref = ref[8:]
} else if strings.HasPrefix(ref, "teatime") {
hour = 16
minute = 16
ref = ref[7:]
}

now := time.Now().UTC()
timeZone := time.UTC
refDate := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, timeZone)

return refDate, ref
}

func stringMatchesListIndex(a string, list []string) int {
for i, b := range list {
if b == a {
return i
}
}
return -1
}
3 changes: 3 additions & 0 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ github.com/hashicorp/hcl/hcl/token
github.com/hashicorp/hcl/json/parser
github.com/hashicorp/hcl/json/scanner
github.com/hashicorp/hcl/json/token
# github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d
## explicit
github.com/leekchan/timeutil
# github.com/lib/pq v1.10.6
## explicit
github.com/lib/pq
Expand Down