From 2c69b335473781c5298490db6ed0a67989cb7cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Wed, 7 Dec 2022 23:15:16 +0100 Subject: [PATCH] v0.0.29 --- timeext/parser.go | 152 +++++++++++++++++++++++++++++++++++++++++ timeext/parser_test.go | 70 +++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 timeext/parser.go create mode 100644 timeext/parser_test.go diff --git a/timeext/parser.go b/timeext/parser.go new file mode 100644 index 0000000..0ed7d6b --- /dev/null +++ b/timeext/parser.go @@ -0,0 +1,152 @@ +package timeext + +import ( + "errors" + "fmt" + "strconv" + "strings" + "time" +) + +var durationShortStringMap = map[string]time.Duration{ + "ns": time.Nanosecond, + "nanosecond": time.Nanosecond, + "nanoseconds": time.Nanosecond, + + "us": time.Microsecond, + "microsecond": time.Microsecond, + "microseconds": time.Microsecond, + + "ms": time.Millisecond, + "millisecond": time.Millisecond, + "milliseconds": time.Millisecond, + + "s": time.Second, + "sec": time.Second, + "second": time.Second, + "seconds": time.Second, + + "m": time.Minute, + "min": time.Minute, + "minute": time.Minute, + "minutes": time.Minute, + + "h": time.Hour, + "hour": time.Hour, + "hours": time.Hour, + + "d": 24 * time.Hour, + "day": 24 * time.Hour, + "days": 24 * time.Hour, + + "w": 7 * 24 * time.Hour, + "wk": 7 * 24 * time.Hour, + "week": 7 * 24 * time.Hour, + "weeks": 7 * 24 * time.Hour, +} + +// ParseDurationShortString parses a duration in string format to a time.Duration +// Examples for allowed formats: +// - '10m' +// - '10min' +// - '1minute' +// - '10minutes' +// - '10.5minutes' +// - '50s' +// - '50sec' +// - '1second' +// - '50seconds' +// - '100ms' +// - '100millisseconds' +// - '1h' +// - '1hour' +// - '2hours' +// - '1d' +// - '1day' +// - '10days' +// - '1d10m' +// - '1d10m200sec' +// - '1d:10m' +// - '1d 10m' +// - '1d,10m' +func ParseDurationShortString(s string) (time.Duration, error) { + s = strings.ToLower(s) + + segments := make([]string, 0) + collector := "" + + prevWasNum := true + for _, chr := range s { + if chr >= '0' && chr <= '9' || chr == '.' { + if prevWasNum { + collector += string(chr) + } else { + segments = append(segments, collector) + prevWasNum = true + collector = string(chr) + } + } else if chr == ' ' || chr == ':' || chr == ',' { + continue + } else if chr >= 'a' && chr <= 'z' { + prevWasNum = false + collector += string(chr) + } else { + return 0, errors.New("unexpected character: " + string(chr)) + } + } + if !prevWasNum { + segments = append(segments, collector) + } + + result := time.Duration(0) + for _, seg := range segments { + segDur, err := parseDurationShortStringSegment(seg) + if err != nil { + return 0, err + } + result += segDur + } + return result, nil +} + +func parseDurationShortStringSegment(segment string) (time.Duration, error) { + num := "" + unit := "" + + part0 := true + for _, chr := range segment { + if part0 { + + if chr >= 'a' && chr <= 'z' { + part0 = false + unit += string(chr) + } else if chr >= '0' && chr <= '9' || chr == '.' { + num += string(chr) + } else { + return 0, errors.New(fmt.Sprintf("Unexpected character '%s' in segment [%s]", string(chr), segment)) + } + + } else { + + if chr >= 'a' && chr <= 'z' { + unit += string(chr) + } else if chr >= '0' && chr <= '9' || chr == '.' { + return 0, errors.New(fmt.Sprintf("Unexpected number '%s' in segment [%s]", string(chr), segment)) + } else { + return 0, errors.New(fmt.Sprintf("Unexpected character '%s' in segment [%s]", string(chr), segment)) + } + + } + } + + fpnum, err := strconv.ParseFloat(num, 64) + if err != nil { + return 0, errors.New(fmt.Sprintf("Failed to parse floating-point number '%s' in segment [%s]", num, segment)) + } + + if mult, ok := durationShortStringMap[unit]; ok { + return time.Duration(int64(fpnum * float64(mult))), nil + } else { + return 0, errors.New(fmt.Sprintf("Unknown unit '%s' in segment [%s]", unit, segment)) + } +} diff --git a/timeext/parser_test.go b/timeext/parser_test.go new file mode 100644 index 0000000..96b300e --- /dev/null +++ b/timeext/parser_test.go @@ -0,0 +1,70 @@ +package timeext + +import ( + "testing" + "time" +) + +func TestParseDurationShortString(t *testing.T) { + + assertPDSSEqual(t, FromSeconds(1), "1s") + assertPDSSEqual(t, FromSeconds(1), "1sec") + assertPDSSEqual(t, FromSeconds(1), "1second") + assertPDSSEqual(t, FromSeconds(1), "1seconds") + assertPDSSEqual(t, FromSeconds(100), "100second") + assertPDSSEqual(t, FromSeconds(100), "100seconds") + assertPDSSEqual(t, FromSeconds(1883639.77), "1883639.77second") + assertPDSSEqual(t, FromSeconds(1883639.77), "1883639.77seconds") + assertPDSSEqual(t, FromSeconds(50), "50s") + assertPDSSEqual(t, FromSeconds(50), "50sec") + assertPDSSEqual(t, FromSeconds(1), "1second") + assertPDSSEqual(t, FromSeconds(50), "50seconds") + + assertPDSSEqual(t, FromMinutes(10), "10m") + assertPDSSEqual(t, FromMinutes(10), "10min") + assertPDSSEqual(t, FromMinutes(1), "1minute") + assertPDSSEqual(t, FromMinutes(10), "10minutes") + assertPDSSEqual(t, FromMinutes(10.5), "10.5minutes") + + assertPDSSEqual(t, FromMilliseconds(100), "100ms") + assertPDSSEqual(t, FromMilliseconds(100), "100milliseconds") + assertPDSSEqual(t, FromMilliseconds(100), "100millisecond") + + assertPDSSEqual(t, FromNanoseconds(99235), "99235ns") + assertPDSSEqual(t, FromNanoseconds(99235), "99235nanoseconds") + assertPDSSEqual(t, FromNanoseconds(99235), "99235nanosecond") + + assertPDSSEqual(t, FromMicroseconds(99235), "99235us") + assertPDSSEqual(t, FromMicroseconds(99235), "99235microseconds") + assertPDSSEqual(t, FromMicroseconds(99235), "99235microsecond") + + assertPDSSEqual(t, FromHours(1), "1h") + assertPDSSEqual(t, FromHours(1), "1hour") + assertPDSSEqual(t, FromHours(2), "2hours") + + assertPDSSEqual(t, FromDays(1), "1d") + assertPDSSEqual(t, FromDays(1), "1day") + assertPDSSEqual(t, FromDays(10), "10days") + assertPDSSEqual(t, FromDays(1), "1days") + assertPDSSEqual(t, FromDays(10), "10day") + + assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d10m") + assertPDSSEqual(t, FromDays(1)+FromMinutes(10)+FromSeconds(200), "1d10m200sec") + assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d:10m") + assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d 10m") + assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d,10m") + assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d, 10m") + assertPDSSEqual(t, FromDays(1)+FromSeconds(1000), "1d 1000seconds") + + assertPDSSEqual(t, FromDays(1), "86400s") +} + +func assertPDSSEqual(t *testing.T, expected time.Duration, fmt string) { + actual, err := ParseDurationShortString(fmt) + if err != nil { + t.Errorf("ParseDurationShortString('%s') failed with %v", fmt, err) + } + if actual != expected { + t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual.String(), expected.String()) + } +}