package cli import ( "errors" "fmt" "time" ) type TimestampFlag = FlagBase[time.Time, TimestampConfig, timestampValue] // TimestampConfig defines the config for timestamp flags type TimestampConfig struct { Timezone *time.Location // Available layouts for flag value. // // Note that value for formats with missing year/date will be interpreted as current year/date respectively. // // Read more about time layouts: https://pkg.go.dev/time#pkg-constants Layouts []string } // timestampValue wrap to satisfy golang's flag interface. type timestampValue struct { timestamp *time.Time hasBeenSet bool layouts []string location *time.Location } var _ ValueCreator[time.Time, TimestampConfig] = timestampValue{} // Below functions are to satisfy the ValueCreator interface func (t timestampValue) Create(val time.Time, p *time.Time, c TimestampConfig) Value { *p = val return ×tampValue{ timestamp: p, layouts: c.Layouts, location: c.Timezone, } } func (t timestampValue) ToString(b time.Time) string { if b.IsZero() { return "" } return fmt.Sprintf("%v", b) } // Timestamp constructor(for internal testing only) func newTimestamp(timestamp time.Time) *timestampValue { return ×tampValue{timestamp: ×tamp} } // Below functions are to satisfy the flag.Value interface // Parses the string value to timestamp func (t *timestampValue) Set(value string) error { var timestamp time.Time var err error if t.location == nil { t.location = time.UTC } if len(t.layouts) == 0 { return errors.New("got nil/empty layouts slice") } for _, layout := range t.layouts { var locErr error timestamp, locErr = time.ParseInLocation(layout, value, t.location) if locErr != nil { if err == nil { err = locErr continue } err = newMultiError(err, locErr) continue } err = nil break } if err != nil { return err } defaultTS, _ := time.ParseInLocation(time.TimeOnly, time.TimeOnly, timestamp.Location()) n := time.Now() // If format is missing date (or year only), set it explicitly to current if timestamp.Truncate(time.Hour*24).UnixNano() == defaultTS.Truncate(time.Hour*24).UnixNano() { timestamp = time.Date( n.Year(), n.Month(), n.Day(), timestamp.Hour(), timestamp.Minute(), timestamp.Second(), timestamp.Nanosecond(), timestamp.Location(), ) } else if timestamp.Year() == 0 { timestamp = time.Date( n.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), timestamp.Minute(), timestamp.Second(), timestamp.Nanosecond(), timestamp.Location(), ) } if t.timestamp != nil { *t.timestamp = timestamp } t.hasBeenSet = true return nil } // String returns a readable representation of this value (for usage defaults) func (t *timestampValue) String() string { return fmt.Sprintf("%#v", t.timestamp) } // Value returns the timestamp value stored in the flag func (t *timestampValue) Value() *time.Time { return t.timestamp } // Get returns the flag structure func (t *timestampValue) Get() any { return *t.timestamp } // Timestamp gets the timestamp from a flag name func (cmd *Command) Timestamp(name string) time.Time { if v, ok := cmd.Value(name).(time.Time); ok { tracef("time.Time available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name) return v } tracef("time.Time NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name) return time.Time{} }