2020-04-03 23:11:33 +00:00
|
|
|
package utils
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
2023-02-11 03:11:40 +00:00
|
|
|
"strings"
|
2020-04-03 23:11:33 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2022-03-02 06:40:26 +00:00
|
|
|
// StandardizeDurationString converts units of time that stdlib is unaware of to hours.
|
|
|
|
func StandardizeDurationString(input string) (output string, err error) {
|
|
|
|
if input == "" {
|
|
|
|
return "0s", nil
|
|
|
|
}
|
|
|
|
|
2023-05-08 05:57:11 +00:00
|
|
|
input = strings.ReplaceAll(input, "and", "")
|
|
|
|
|
2023-02-11 03:11:40 +00:00
|
|
|
matches := reDurationStandard.FindAllStringSubmatch(strings.ReplaceAll(input, " ", ""), -1)
|
2022-03-02 06:40:26 +00:00
|
|
|
|
|
|
|
if len(matches) == 0 {
|
|
|
|
return "", fmt.Errorf("could not parse '%s' as a duration", input)
|
|
|
|
}
|
|
|
|
|
2023-02-11 03:11:40 +00:00
|
|
|
var (
|
|
|
|
o string
|
|
|
|
q int
|
|
|
|
)
|
2022-03-02 06:40:26 +00:00
|
|
|
|
|
|
|
for _, match := range matches {
|
2023-02-11 03:11:40 +00:00
|
|
|
if q, err = strconv.Atoi(match[1]); err != nil {
|
|
|
|
return "", err
|
2020-04-03 23:11:33 +00:00
|
|
|
}
|
2022-03-02 06:40:26 +00:00
|
|
|
|
2023-02-11 03:11:40 +00:00
|
|
|
if o, err = standardizeQuantityAndUnits(q, match[2]); err != nil {
|
|
|
|
return "", fmt.Errorf("could not parse the units portion of '%s' in duration string '%s': %w", match[0], input, err)
|
2020-04-03 23:11:33 +00:00
|
|
|
}
|
2023-02-11 03:11:40 +00:00
|
|
|
|
|
|
|
output += o
|
2022-03-02 06:40:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return output, nil
|
|
|
|
}
|
|
|
|
|
2023-02-11 03:11:40 +00:00
|
|
|
func standardizeQuantityAndUnits(qty int, unit string) (output string, err error) {
|
|
|
|
switch {
|
|
|
|
case IsStringInSlice(unit, standardDurationUnits):
|
|
|
|
return fmt.Sprintf("%d%s", qty, unit), nil
|
|
|
|
case len(unit) == 1:
|
|
|
|
switch unit {
|
|
|
|
case DurationUnitDays:
|
|
|
|
return fmt.Sprintf("%dh", qty*HoursInDay), nil
|
|
|
|
case DurationUnitWeeks:
|
|
|
|
return fmt.Sprintf("%dh", qty*HoursInWeek), nil
|
|
|
|
case DurationUnitMonths:
|
|
|
|
return fmt.Sprintf("%dh", qty*HoursInMonth), nil
|
|
|
|
case DurationUnitYears:
|
|
|
|
return fmt.Sprintf("%dh", qty*HoursInYear), nil
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
switch unit {
|
|
|
|
case "millisecond", "milliseconds":
|
|
|
|
return fmt.Sprintf("%dms", qty), nil
|
|
|
|
case "second", "seconds":
|
|
|
|
return fmt.Sprintf("%ds", qty), nil
|
|
|
|
case "minute", "minutes":
|
|
|
|
return fmt.Sprintf("%dm", qty), nil
|
|
|
|
case "hour", "hours":
|
|
|
|
return fmt.Sprintf("%dh", qty), nil
|
|
|
|
case "day", "days":
|
|
|
|
return fmt.Sprintf("%dh", qty*HoursInDay), nil
|
|
|
|
case "week", "weeks":
|
|
|
|
return fmt.Sprintf("%dh", qty*HoursInWeek), nil
|
|
|
|
case "month", "months":
|
|
|
|
return fmt.Sprintf("%dh", qty*HoursInMonth), nil
|
|
|
|
case "year", "years":
|
|
|
|
return fmt.Sprintf("%dh", qty*HoursInYear), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("the unit '%s' is not valid", unit)
|
|
|
|
}
|
|
|
|
|
2022-03-02 06:40:26 +00:00
|
|
|
// ParseDurationString standardizes a duration string with StandardizeDurationString then uses time.ParseDuration to
|
|
|
|
// convert it into a time.Duration.
|
|
|
|
func ParseDurationString(input string) (duration time.Duration, err error) {
|
2023-02-11 03:11:40 +00:00
|
|
|
if reOnlyNumeric.MatchString(input) {
|
2022-03-02 06:40:26 +00:00
|
|
|
var seconds int
|
|
|
|
|
|
|
|
if seconds, err = strconv.Atoi(input); err != nil {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Second * time.Duration(seconds), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var out string
|
2020-05-06 00:52:06 +00:00
|
|
|
|
2022-03-02 06:40:26 +00:00
|
|
|
if out, err = StandardizeDurationString(input); err != nil {
|
|
|
|
return 0, err
|
2020-04-03 23:11:33 +00:00
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2022-03-02 06:40:26 +00:00
|
|
|
return time.ParseDuration(out)
|
2020-04-03 23:11:33 +00:00
|
|
|
}
|
2022-12-21 10:31:21 +00:00
|
|
|
|
2023-02-11 03:11:40 +00:00
|
|
|
// ParseTimeString attempts to parse a string with several time formats.
|
|
|
|
func ParseTimeString(input string) (t time.Time, err error) {
|
|
|
|
return ParseTimeStringWithLayouts(input, StandardTimeLayouts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseTimeStringWithLayouts attempts to parse a string with several time formats. The format with the most matching
|
|
|
|
// characters is returned.
|
|
|
|
func ParseTimeStringWithLayouts(input string, layouts []string) (match time.Time, err error) {
|
|
|
|
_, match, err = matchParseTimeStringWithLayouts(input, layouts)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func matchParseTimeStringWithLayouts(input string, layouts []string) (index int, match time.Time, err error) {
|
|
|
|
if reOnlyNumeric.MatchString(input) {
|
|
|
|
var u int64
|
|
|
|
|
|
|
|
if u, err = strconv.ParseInt(input, 10, 64); err != nil {
|
|
|
|
return -999, match, fmt.Errorf("time value was detected as an integer but the integer could not be parsed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case u > 32503554000000: // 2999-12-31 00:00:00 in unix time (milliseconds).
|
|
|
|
return -3, time.UnixMicro(u), nil
|
|
|
|
case u > 946645200000: // 2000-01-01 00:00:00 in unix time (milliseconds).
|
|
|
|
return -2, time.UnixMilli(u), nil
|
|
|
|
default:
|
|
|
|
return -1, time.Unix(u, 0), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var layout string
|
|
|
|
|
|
|
|
for index, layout = range layouts {
|
|
|
|
if match, err = time.Parse(layout, input); err == nil {
|
|
|
|
if len(match.Format(layout))-len(input) == 0 {
|
|
|
|
return index, match, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -998, time.UnixMilli(0), fmt.Errorf("failed to find a suitable time layout for time '%s'", input)
|
|
|
|
}
|
|
|
|
|
2022-12-21 10:31:21 +00:00
|
|
|
// UnixNanoTimeToMicrosoftNTEpoch converts a unix timestamp in nanosecond format to win32 epoch format.
|
|
|
|
func UnixNanoTimeToMicrosoftNTEpoch(nano int64) (t uint64) {
|
|
|
|
return uint64(nano/100) + timeUnixEpochAsMicrosoftNTEpoch
|
|
|
|
}
|