mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 02:57:59 +00:00
Add date/time argument type. (#9817)
* Add date/time argument type. * Add an argument to select which time formats are valid. * Increase minimum date for epoch timestamps to avoid ambiguity.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -749,6 +750,135 @@ func (f *FlagSet) Var(value flag.Value, name, usage string) {
|
||||
f.flagSet.Var(value, name, usage)
|
||||
}
|
||||
|
||||
// -- TimeVar and timeValue
|
||||
type TimeVar struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
Usage string
|
||||
Default time.Time
|
||||
Hidden bool
|
||||
EnvVar string
|
||||
Target *time.Time
|
||||
Completion complete.Predictor
|
||||
Formats TimeFormat
|
||||
}
|
||||
|
||||
// Identify the allowable formats, identified by the minimum
|
||||
// precision accepted.
|
||||
// TODO: move this somewhere where it can be re-used for the API.
|
||||
type TimeFormat int
|
||||
|
||||
const (
|
||||
TimeVar_EpochSecond TimeFormat = 1 << iota
|
||||
TimeVar_RFC3339Nano
|
||||
TimeVar_RFC3339Second
|
||||
TimeVar_Day
|
||||
TimeVar_Month
|
||||
)
|
||||
|
||||
// Default value to use
|
||||
const TimeVar_TimeOrDay TimeFormat = TimeVar_EpochSecond | TimeVar_RFC3339Nano | TimeVar_RFC3339Second | TimeVar_Day
|
||||
|
||||
// parseTimeAlternatives attempts several different allowable variants
|
||||
// of the time field.
|
||||
func parseTimeAlternatives(input string, allowedFormats TimeFormat) (time.Time, error) {
|
||||
// The RFC3339 formats require the inclusion of a time zone.
|
||||
if allowedFormats&TimeVar_RFC3339Nano != 0 {
|
||||
t, err := time.Parse(time.RFC3339Nano, input)
|
||||
if err == nil {
|
||||
return t, err
|
||||
}
|
||||
}
|
||||
|
||||
if allowedFormats&TimeVar_RFC3339Second != 0 {
|
||||
t, err := time.Parse(time.RFC3339, input)
|
||||
if err == nil {
|
||||
return t, err
|
||||
}
|
||||
}
|
||||
|
||||
if allowedFormats&TimeVar_Day != 0 {
|
||||
t, err := time.Parse("2006-01-02", input)
|
||||
if err == nil {
|
||||
return t, err
|
||||
}
|
||||
}
|
||||
|
||||
if allowedFormats&TimeVar_Month != 0 {
|
||||
t, err := time.Parse("2006-01", input)
|
||||
if err == nil {
|
||||
return t, err
|
||||
}
|
||||
}
|
||||
|
||||
if allowedFormats&TimeVar_EpochSecond != 0 {
|
||||
i, err := strconv.ParseInt(input, 10, 64)
|
||||
if err == nil {
|
||||
// If a customer enters 20200101 we don't want
|
||||
// to parse that as an epoch time.
|
||||
// This arbitrarily-chosen cutoff is around year 2000.
|
||||
if i > 946000000 {
|
||||
return time.Unix(i, 0), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return time.Time{}, errors.New("Could not parse as absolute time.")
|
||||
}
|
||||
|
||||
func (f *FlagSet) TimeVar(i *TimeVar) {
|
||||
initial := i.Default
|
||||
if v, exist := os.LookupEnv(i.EnvVar); exist {
|
||||
if d, err := parseTimeAlternatives(v, i.Formats); err == nil {
|
||||
initial = d
|
||||
}
|
||||
}
|
||||
|
||||
def := ""
|
||||
if !i.Default.IsZero() {
|
||||
def = i.Default.String()
|
||||
}
|
||||
|
||||
f.VarFlag(&VarFlag{
|
||||
Name: i.Name,
|
||||
Aliases: i.Aliases,
|
||||
Usage: i.Usage,
|
||||
Default: def,
|
||||
EnvVar: i.EnvVar,
|
||||
Value: newTimeValue(initial, i.Target, i.Hidden, i.Formats),
|
||||
Completion: i.Completion,
|
||||
})
|
||||
}
|
||||
|
||||
type timeValue struct {
|
||||
hidden bool
|
||||
target *time.Time
|
||||
formats TimeFormat
|
||||
}
|
||||
|
||||
func newTimeValue(def time.Time, target *time.Time, hidden bool, f TimeFormat) *timeValue {
|
||||
*target = def
|
||||
return &timeValue{
|
||||
hidden: hidden,
|
||||
target: target,
|
||||
formats: f,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *timeValue) Set(s string) error {
|
||||
v, err := parseTimeAlternatives(s, d.formats)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d.target = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *timeValue) Get() interface{} { return *d.target }
|
||||
func (d *timeValue) String() string { return (*d.target).String() }
|
||||
func (d *timeValue) Example() string { return "time" }
|
||||
func (d *timeValue) Hidden() bool { return d.hidden }
|
||||
|
||||
// -- helpers
|
||||
func envDefault(key, def string) string {
|
||||
if v, exist := os.LookupEnv(key); exist {
|
||||
|
||||
115
command/base_flags_test.go
Normal file
115
command/base_flags_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_TimeParsing(t *testing.T) {
|
||||
var zeroTime time.Time
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Formats TimeFormat
|
||||
Valid bool
|
||||
Expected time.Time
|
||||
}{
|
||||
{
|
||||
"2020-08-24",
|
||||
TimeVar_TimeOrDay,
|
||||
true,
|
||||
time.Date(2020, 8, 24, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
"2099-09",
|
||||
TimeVar_TimeOrDay,
|
||||
false,
|
||||
zeroTime,
|
||||
},
|
||||
{
|
||||
"2099-09",
|
||||
TimeVar_TimeOrDay | TimeVar_Month,
|
||||
true,
|
||||
time.Date(2099, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
"2021-01-02T03:04:05-02:00",
|
||||
TimeVar_TimeOrDay,
|
||||
true,
|
||||
time.Date(2021, 1, 2, 5, 4, 5, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
"2021-01-02T03:04:05",
|
||||
TimeVar_TimeOrDay,
|
||||
false, // Missing timezone not supported
|
||||
time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
"2021-01-02T03:04:05+02:00",
|
||||
TimeVar_TimeOrDay,
|
||||
true,
|
||||
time.Date(2021, 1, 2, 1, 4, 5, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
"1598313593",
|
||||
TimeVar_TimeOrDay,
|
||||
true,
|
||||
time.Date(2020, 8, 24, 23, 59, 53, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
"2037",
|
||||
TimeVar_TimeOrDay,
|
||||
false,
|
||||
zeroTime,
|
||||
},
|
||||
{
|
||||
"20201212",
|
||||
TimeVar_TimeOrDay,
|
||||
false,
|
||||
zeroTime,
|
||||
},
|
||||
{
|
||||
"9999999999999999999999999999999999999999999999",
|
||||
TimeVar_TimeOrDay,
|
||||
false,
|
||||
zeroTime,
|
||||
},
|
||||
{
|
||||
"2021-13-02T03:04:05-02:00",
|
||||
TimeVar_TimeOrDay,
|
||||
false,
|
||||
zeroTime,
|
||||
},
|
||||
{
|
||||
"2021-12-02T24:04:05+00:00",
|
||||
TimeVar_TimeOrDay,
|
||||
false,
|
||||
zeroTime,
|
||||
},
|
||||
{
|
||||
"2021-01-02T03:04:05.234567890Z",
|
||||
TimeVar_TimeOrDay,
|
||||
true,
|
||||
time.Date(2021, 1, 2, 3, 4, 5, 234567890, time.UTC),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
var result time.Time
|
||||
timeVal := newTimeValue(zeroTime, &result, false, tc.Formats)
|
||||
err := timeVal.Set(tc.Input)
|
||||
if err == nil && !tc.Valid {
|
||||
t.Errorf("Time %q parsed without error as %v, but is not valid", tc.Input, result)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
if tc.Valid {
|
||||
t.Errorf("Time %q parsed as error, but is valid", tc.Input)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !tc.Expected.Equal(result) {
|
||||
t.Errorf("Time %q parsed incorrectly, expected %v but got %v", tc.Input, tc.Expected.UTC(), result.UTC())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user