From ca365ad39cd35dafd987acc5d7d125db4a04bce0 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 4 Mar 2026 08:53:02 -0700 Subject: [PATCH] can pass duration to period --- cmd/cli/flag.go | 58 +++++++++++++++++++++++++++++++++++++++++--- cmd/cli/flag_test.go | 41 +++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 cmd/cli/flag_test.go diff --git a/cmd/cli/flag.go b/cmd/cli/flag.go index a44eab3..d705594 100644 --- a/cmd/cli/flag.go +++ b/cmd/cli/flag.go @@ -3,6 +3,8 @@ package cli import ( "fmt" "os" + "regexp" + "strconv" "strings" "time" ) @@ -63,21 +65,71 @@ func (period *Period) Set(s string) error { } func (period *Period) setStartStop(s string) error { - stop, err := period.setT(s, &period.Start) + stop, err := period.setT(time.Now(), s, &period.Start) period.Stop = stop return err } func (period *Period) setStop(s string) error { - _, err := period.setT(s, &period.Stop) + _, err := period.setT(time.Now(), s, &period.Stop) return err } -func (*Period) setT(s string, t *time.Time) (time.Time, error) { +func (*Period) setT(now time.Time, s string, t *time.Time) (time.Time, error) { if s == "" { *t = time.Unix(0, 0) return time.Now().AddDate(100, 0, 0), nil } + if submatches := regexp.MustCompile(`[+-]?(?P[0-9]+y)?(?P[0-9]+mo)?(?P[0-9]+w)?(?P[0-9]+d)?([0-9]+[a-z])?`).FindStringSubmatch(s); len(submatches) > 0 { + s2 := s + result := now + scalar := time.Duration(1) + if strings.HasPrefix(s2, "-") { + scalar = -1 + } + + for i, submatch := range submatches[1 : len(submatches)-1] { + if len(submatch) == 0 { + continue + } + + unit := submatch[len(submatch)-1] + if submatch[len(submatch)-1] == 'o' { // mo + submatch = submatch[:len(submatch)-1] + } + + n, err := strconv.Atoi(submatch[:len(submatch)-1]) + if err != nil { + return time.Time{}, err + } + + for i := 0; i < n; i++ { + switch unit { + case 'y': + result = result.AddDate(int(scalar*1), 0, 0) + case 'o': + result = result.AddDate(0, int(scalar*1), 0) + case 'w': + result = result.AddDate(0, 0, int(scalar*7)) + case 'd': + result = result.AddDate(0, 0, int(scalar*1)) + } + } + + s2 = strings.ReplaceAll(s2, submatches[i+1], "") + } + + if stdDuration := strings.TrimLeft(s2, "+-"); stdDuration != "" { + if d, err := time.ParseDuration(stdDuration); err == nil { + result = result.Add(scalar * d) + } + } + + if result != now { + *t = result + return result.AddDate(100, 0, 0), nil + } + } if result, err := time.Parse("2006", s); err == nil { *t = result return result.AddDate(1, 0, 0).Add(-1 * time.Minute), nil diff --git a/cmd/cli/flag_test.go b/cmd/cli/flag_test.go new file mode 100644 index 0000000..c2b7b7e --- /dev/null +++ b/cmd/cli/flag_test.go @@ -0,0 +1,41 @@ +package cli + +import ( + "testing" + "time" +) + +func TestPeriodSetT(t *testing.T) { + now := time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC) + cases := map[string]time.Time{ + "2001-02-03": time.Date(2001, 2, 3, 0, 0, 0, 0, time.UTC), + "2001-02": time.Date(2001, 2, 1, 0, 0, 0, 0, time.UTC), + "2001": time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC), + "": time.Unix(0, 0), + "1m": now.Add(1 * time.Minute), + "+1m": now.Add(1 * time.Minute), + "-1m": now.Add(-1 * time.Minute), + "1h1m": now.Add(1*time.Hour + 1*time.Minute), + "1d": now.Add(24 * time.Hour), + "+1d": now.Add(24 * time.Hour), + "-1d": now.Add(-24 * time.Hour), + "1d1m": now.Add(24*time.Hour + 1*time.Minute), + "+1d1m": now.Add(24*time.Hour + 1*time.Minute), + "-1d1m": now.Add(-1*24*time.Hour + -1*1*time.Minute), + "1y1mo1w1d1h": now.AddDate(1, 1, 7+1).Add(1 * time.Hour), + } + + for input, expect := range cases { + input, expect := input, expect + t.Run(input, func(t *testing.T) { + p := &Period{} + var got time.Time + if _, err := p.setT(now, input, &got); err != nil { + t.Fatal(err) + } + if got.Unix() != expect.Unix() { + t.Errorf("expected %s but got %s", expect.String(), got.String()) + } + }) + } +}