mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 05:40:11 +01:00
Accept days, months and years in time ranges. (#483)
This commit is contained in:
parent
81edb6ccbe
commit
455010dc64
3 changed files with 105 additions and 8 deletions
|
|
@ -12,7 +12,10 @@ package models
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -59,6 +62,65 @@ func (tr *TimeRange) UnmarshalText(text []byte) error {
|
|||
return tr.UnmarshalFlag(string(text))
|
||||
}
|
||||
|
||||
var (
|
||||
yearsMonthsDays *regexp.Regexp
|
||||
yearsMonthsDaysOnce sync.Once
|
||||
)
|
||||
|
||||
// parseDuration extends time.ParseDuration with recognition of
|
||||
// years, month and days with the suffixes "y", "M" and "d".
|
||||
// Onlys integer values are detected. The handling of fractional
|
||||
// values would increase the complexity and may be done in the future.
|
||||
// The calculate dates are assumed to be before the reference time.
|
||||
func parseDuration(s string, reference time.Time) (time.Duration, error) {
|
||||
var (
|
||||
extra time.Duration
|
||||
err error
|
||||
used bool
|
||||
)
|
||||
parse := func(s string) int {
|
||||
if err == nil {
|
||||
var v int
|
||||
v, err = strconv.Atoi(s)
|
||||
return v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// Only compile expression if really needed.
|
||||
yearsMonthsDaysOnce.Do(func() {
|
||||
yearsMonthsDays = regexp.MustCompile(`[-+]?[0-9]+[yMd]`)
|
||||
})
|
||||
s = yearsMonthsDays.ReplaceAllStringFunc(s, func(part string) string {
|
||||
used = true
|
||||
var years, months, days int
|
||||
switch suf, num := part[len(part)-1], part[:len(part)-1]; suf {
|
||||
case 'y':
|
||||
years = -parse(num)
|
||||
case 'M':
|
||||
months = -parse(num)
|
||||
case 'd':
|
||||
days = -parse(num)
|
||||
}
|
||||
date := reference.AddDate(years, months, days)
|
||||
extra += reference.Sub(date)
|
||||
// Remove from string
|
||||
return ""
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// If there is no rest we don't need the stdlib parser.
|
||||
if used && s == "" {
|
||||
return extra, nil
|
||||
}
|
||||
// Parse the rest with the stdlib.
|
||||
d, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
return d + extra, nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements [encoding/json.Marshaler].
|
||||
func (tr TimeRange) MarshalJSON() ([]byte, error) {
|
||||
s := []string{
|
||||
|
|
@ -72,9 +134,10 @@ func (tr TimeRange) MarshalJSON() ([]byte, error) {
|
|||
func (tr *TimeRange) UnmarshalFlag(s string) error {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// Handle relative case first.
|
||||
if duration, err := time.ParseDuration(s); err == nil {
|
||||
now := time.Now()
|
||||
if duration, err := parseDuration(s, now); err == nil {
|
||||
*tr = NewTimeInterval(now.Add(-duration), now)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -88,7 +151,7 @@ func (tr *TimeRange) UnmarshalFlag(s string) error {
|
|||
if !ok {
|
||||
return fmt.Errorf("%q is not a valid RFC date time", a)
|
||||
}
|
||||
*tr = NewTimeInterval(start, time.Now())
|
||||
*tr = NewTimeInterval(start, now)
|
||||
return nil
|
||||
}
|
||||
// Real interval
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -25,6 +26,36 @@ func TestNewTimeInterval(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseDuration(t *testing.T) {
|
||||
|
||||
now := time.Now()
|
||||
|
||||
for _, x := range []struct {
|
||||
in string
|
||||
expected time.Duration
|
||||
reference time.Time
|
||||
fail bool
|
||||
}{
|
||||
{"1h", time.Hour, now, false},
|
||||
{"2y", now.Sub(now.AddDate(-2, 0, 0)), now, false},
|
||||
{"13M", now.Sub(now.AddDate(0, -13, 0)), now, false},
|
||||
{"31d", now.Sub(now.AddDate(0, 0, -31)), now, false},
|
||||
{"1h2d3m", now.Sub(now.AddDate(0, 0, -2)) + time.Hour + 3*time.Minute, now, false},
|
||||
{strings.Repeat("1", 70) + "y1d", 0, now, true},
|
||||
} {
|
||||
got, err := parseDuration(x.in, x.reference)
|
||||
if err != nil {
|
||||
if !x.fail {
|
||||
t.Errorf("%q should not fail: %v", x.in, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if got != x.expected {
|
||||
t.Errorf("%q got %v expected %v", x.in, got, x.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGuessDate tests whether a sample of strings are correctly parsed into Dates by guessDate()
|
||||
func TestGuessDate(t *testing.T) {
|
||||
if _, guess := guessDate("2006-01-02T15:04:05"); !guess {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue