Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2910,6 +2910,27 @@ func TestTimestampFlagApply_YearlessLayoutJanuaryFirst(t *testing.T) {
}
}

func TestTimestampFlagApply_ExplicitYearZero(t *testing.T) {
for _, tc := range []struct{ layout, value string }{
{time.RFC3339, "0000-02-29T12:34:56Z"},
{time.DateOnly, "0000-01-01"},
{"2006 15:04", "0000 12:34"},
} {
t.Run(tc.layout, func(t *testing.T) {
want, err := time.Parse(tc.layout, tc.value)
require.NoError(t, err)
cmd := &Command{
Flags: []Flag{&TimestampFlag{Name: "time", Config: TimestampConfig{Layouts: []string{tc.layout}}}},
Action: func(_ context.Context, cmd *Command) error {
assert.Equal(t, want, cmd.Timestamp("time"))
return nil
},
}
assert.NoError(t, cmd.Run(buildTestContext(t), []string{"app", "--time", tc.value}))
})
}
}

func TestTimestampFlagApplyValue(t *testing.T) {
expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Config: TimestampConfig{Layouts: []string{time.RFC3339}}, Value: expectedResult}
Expand Down
19 changes: 12 additions & 7 deletions flag_timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ func (t *timestampValue) Set(value string) error {
// The date is compared in the timestamp's own location: truncating the
// instant to a day works in UTC and misses January 1 of year 0 for part
// of the day whenever Timezone is not UTC.
if !layoutHasDate(matchedLayout) && timestamp.Year() == 0 && timestamp.YearDay() == 1 {
hasYear, hasDate := layoutFields(matchedLayout)
yearMissing := timestamp.Year() == 0 && !hasYear
if !hasDate && yearMissing && timestamp.YearDay() == 1 {
timestamp = time.Date(
n.Year(),
n.Month(),
Expand All @@ -105,7 +107,7 @@ func (t *timestampValue) Set(value string) error {
timestamp.Nanosecond(),
timestamp.Location(),
)
} else if timestamp.Year() == 0 {
} else if yearMissing {
timestamp = time.Date(
n.Year(),
timestamp.Month(),
Expand All @@ -125,12 +127,15 @@ func (t *timestampValue) Set(value string) error {
return nil
}

// layoutHasDate reports whether layout carries month/day components, probed by
// round-tripping a reference date that is not January 1.
func layoutHasDate(layout string) bool {
// layoutFields reports whether layout carries a year and month/day components,
// probed by round-tripping a reference date that is not January 1 of year 0.
func layoutFields(layout string) (hasYear, hasDate bool) {
ref := time.Date(2, time.March, 4, 5, 6, 7, 0, time.UTC)
p, err := time.Parse(layout, ref.Format(layout))
return err == nil && (p.Month() != time.January || p.Day() != 1)
if p, err := time.Parse(layout, ref.Format(layout)); err == nil {
hasYear = p.Year() != 0
hasDate = p.Month() != time.January || p.Day() != 1
}
return hasYear, hasDate
}

// String returns a readable representation of this value (for usage defaults)
Expand Down
Loading