Skip to content

Fix month parsing overflowing into the next month - #50

Open
dualfroz wants to merge 1 commit into
olebedev:masterfrom
dualfroz:fix/month-day-overflow
Open

Fix month parsing overflowing into the next month#50
dualfroz wants to merge 1 commit into
olebedev:masterfrom
dualfroz:fix/month-day-overflow

Conversation

@dualfroz

@dualfroz dualfroz commented Sep 5, 2026

Copy link
Copy Markdown

Problem

Fixes #12

Problem

Parsing a bare month, or a month with a day, against a reference date whose day
does not exist in the target month resolves to the wrong month. With the 31st of
October as the reference date:

input got expected
september 5th 2016-10-05 2016-09-05
february 11 2016-03-11 2016-02-11
1st of june 2016-07-01 2016-06-01

This is why the issue reports "September" turning into October and why a later
comment reports February turning into March at the end of January: the bug only
shows up when the reference date is a day that the parsed month does not have,
which is also why it was not reproducible with an arbitrary fixed date.

The last comment in the thread asks whether this has already been fixed. It has
not - the results above come from a run against current master (faea3c4).

Root cause

rules/context.go, the c.Month branch of Context.Time (lines 31-34 before
this change):

if c.Month != nil {
    t = time.Date(t.Year(), time.Month(*c.Month), t.Day(),
        t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
}

The day passed to time.Date is t.Day(), still the day of the reference
date, because the parsed day is only applied further down in the same function
(the c.Day branch). time.Date normalizes out-of-range days, so October 31st
plus month September becomes October 1st, and the c.Day branch then applies
the parsed day to the already wrong month.

Solution

Clamp the reference day to the last day the target month actually has before
switching the month. The parsed day, when the text carries one, still overwrites
it afterwards, so the ordering of the existing branches is untouched:

  • when the text has a day (september 5th), the clamped value is a throwaway
    placeholder that only has to stay inside the target month;
  • when the text has no day (september), the result is the last day of the
    month instead of a silent jump into the next one.

A small daysIn helper does the lookup with the usual time.Date(y, m+1, 0)
trick. No rule files were touched.

How this was tested

Gate, on the fixed tree:

52 test cases pass, no pre-existing failures, gofmt -l . is clean.

New regression test TestExactMonthDateFromLongMonth in
rules/en/exact_month_date_test.go. The existing fixtures cannot catch this
bug: ApplyFixtures pins the reference date to 2016-01-06, and the 6th exists
in every month, so the overflow never triggers. The new test parses against
2016-10-31 instead and checks the resulting dates directly.

With the fix reverted and the test in place:

=== RUN   TestExactMonthDateFromLongMonth
    Error:      Not equal:
                expected: time.Date(2016, time.September, 5, 0, 0, 0, 0, time.UTC)
                actual  : time.Date(2016, time.October, 5, 0, 0, 0, 0, time.UTC)
    Messages:   [en.ExactMonthDate] time #0
--- FAIL: TestExactMonthDateFromLongMonth (0.00s)

With the fix applied:

=== RUN   TestExactMonthDateFromLongMonth
--- PASS: TestExactMonthDateFromLongMonth (0.00s)

Go 1.22.2.

@dualfroz
dualfroz requested a review from olebedev as a code owner September 5, 2026 18:11
Context.Time applied the parsed month while the day was still the one of
the reference date. When that day does not exist in the target month,
time.Date normalizes it forward, so "september 5th" parsed on the 31st
of October resolved to the 5th of October. The parsed day was only
applied afterwards, to the already wrong month.

Clamp the reference day to the last day of the target month before
switching the month. The parsed day still overwrites it afterwards, so
the ordering of the branches in Context.Time is unchanged.

The existing fixtures pin the reference date to 2016-01-06 and the 6th
exists in every month, so the overflow never triggered. Add a regression
test that parses against 2016-10-31.

Fixes olebedev#12
@dualfroz
dualfroz force-pushed the fix/month-day-overflow branch from 9fc1f12 to dcaedc8 Compare September 5, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"September" always incorrectly parsed as October

1 participant