diff --git a/rules/context.go b/rules/context.go index e0a9e2b..67c4641 100644 --- a/rules/context.go +++ b/rules/context.go @@ -29,7 +29,16 @@ func (c *Context) Time(t time.Time) (time.Time, error) { } if c.Month != nil { - t = time.Date(t.Year(), time.Month(*c.Month), t.Day(), + month := time.Month(*c.Month) + + // time.Date would normalize a day the target month lacks into the next + // one; clamp instead. + day := t.Day() + if last := daysIn(month, t.Year()); day > last { + day = last + } + + t = time.Date(t.Year(), month, day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) } @@ -66,3 +75,8 @@ func (c *Context) Time(t time.Time) (time.Time, error) { return t, nil } + +// daysIn returns the number of days in the given month of the given year. +func daysIn(m time.Month, year int) int { + return time.Date(year, m+1, 0, 0, 0, 0, 0, time.UTC).Day() +} diff --git a/rules/en/exact_month_date_test.go b/rules/en/exact_month_date_test.go index 38b3dff..d7f9d91 100644 --- a/rules/en/exact_month_date_test.go +++ b/rules/en/exact_month_date_test.go @@ -7,6 +7,7 @@ import ( "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/en" + "github.com/stretchr/testify/require" ) func TestExactMonthDate(t *testing.T) { @@ -37,3 +38,32 @@ func TestExactMonthDate(t *testing.T) { ApplyFixtures(t, "en.ExactMonthDate", w, fixtok) } + +// Parsed against the 31st, a shorter target month used to overflow into the +// next one: "september 5th" resolved to the 5th of October. +func TestExactMonthDateFromLongMonth(t *testing.T) { + w := when.New(nil) + w.Add(en.ExactMonthDate(rules.Override)) + + ref := time.Date(2016, time.October, 31, 0, 0, 0, 0, time.UTC) + + fixt := []struct { + Text string + Expected time.Time + }{ + {"september 5th", time.Date(2016, time.September, 5, 0, 0, 0, 0, time.UTC)}, + {"february 11", time.Date(2016, time.February, 11, 0, 0, 0, 0, time.UTC)}, + {"1st of june", time.Date(2016, time.June, 1, 0, 0, 0, 0, time.UTC)}, + // No day in the text: the reference day clamps to the month's last. + {"september", time.Date(2016, time.September, 30, 0, 0, 0, 0, time.UTC)}, + // Months that do have a 31st keep the reference day untouched. + {"december", time.Date(2016, time.December, 31, 0, 0, 0, 0, time.UTC)}, + } + + for i, f := range fixt { + res, err := w.Parse(f.Text, ref) + require.Nil(t, err, "[en.ExactMonthDate] err #%d", i) + require.NotNil(t, res, "[en.ExactMonthDate] res #%d", i) + require.Equal(t, f.Expected, res.Time, "[en.ExactMonthDate] time #%d", i) + } +}