From e5a9dc0b2f0c842742af06696c9a39044d945dc9 Mon Sep 17 00:00:00 2001 From: Sahil Rajput <90515841+SahilRajputdotexe@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:00:27 +0530 Subject: [PATCH] fix(rules): carry the year when adding relative months The deadline and past time rules reduced the target month modulo 12, so the year was never carried: with a reference date in October, "in 3 months" resolved to January of the same year, and "in 2 months" to December of the previous one. Drop the modulo and let time.Date normalize the month, which is what Context.Time already relies on. --- rules/br/deadline.go | 4 ++-- rules/br/past_time.go | 4 ++-- rules/en/deadline.go | 4 ++-- rules/en/deadline_test.go | 24 ++++++++++++++++++++++++ rules/en/past_time.go | 4 ++-- rules/en/past_time_test.go | 23 +++++++++++++++++++++++ rules/nl/deadline.go | 4 ++-- rules/nl/past_time.go | 4 ++-- rules/ru/deadline.go | 4 ++-- 9 files changed, 61 insertions(+), 14 deletions(-) diff --git a/rules/br/deadline.go b/rules/br/deadline.go index 3d1d639..4e9ec90 100644 --- a/rules/br/deadline.go +++ b/rules/br/deadline.go @@ -65,7 +65,7 @@ func Deadline(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "mês"), strings.Contains(exponent, "meses"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) + num) % 12) + c.Month = pointer.ToInt(int(ref.Month()) + num) } case strings.Contains(exponent, "ano"): if c.Year == nil || overwrite { @@ -93,7 +93,7 @@ func Deadline(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "ano"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) + 6) % 12) + c.Month = pointer.ToInt(int(ref.Month()) + 6) } } } diff --git a/rules/br/past_time.go b/rules/br/past_time.go index ec10874..f7f2832 100644 --- a/rules/br/past_time.go +++ b/rules/br/past_time.go @@ -79,7 +79,7 @@ func PastTime(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "mês"), strings.Contains(exponent, "meses"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) - num) % 12) + c.Month = pointer.ToInt(int(ref.Month()) - num) } case strings.Contains(exponent, "ano"): if c.Year == nil || overwrite { @@ -107,7 +107,7 @@ func PastTime(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "ano"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) - 6) % 12) + c.Month = pointer.ToInt(int(ref.Month()) - 6) } } } diff --git a/rules/en/deadline.go b/rules/en/deadline.go index 2996586..c16ff62 100644 --- a/rules/en/deadline.go +++ b/rules/en/deadline.go @@ -68,7 +68,7 @@ func Deadline(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "month"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) + num) % 12) + c.Month = pointer.ToInt(int(ref.Month()) + num) } case strings.Contains(exponent, "year"): if c.Year == nil || overwrite { @@ -96,7 +96,7 @@ func Deadline(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "year"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) + 6) % 12) + c.Month = pointer.ToInt(int(ref.Month()) + 6) } } } diff --git a/rules/en/deadline_test.go b/rules/en/deadline_test.go index bebb883..398c87b 100644 --- a/rules/en/deadline_test.go +++ b/rules/en/deadline_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 TestDeadline(t *testing.T) { @@ -31,3 +32,26 @@ func TestDeadline(t *testing.T) { ApplyFixtures(t, "en.Deadline", w, fixt) } + +func TestDeadlineMonthsCrossYear(t *testing.T) { + ref := time.Date(2016, time.October, 6, 0, 0, 0, 0, time.UTC) + fixt := []struct { + text string + want time.Time + }{ + {"in 2 months", time.Date(2016, time.December, 6, 0, 0, 0, 0, time.UTC)}, + {"in 3 months", time.Date(2017, time.January, 6, 0, 0, 0, 0, time.UTC)}, + {"in 14 months", time.Date(2017, time.December, 6, 0, 0, 0, 0, time.UTC)}, + {"within half a year", time.Date(2017, time.April, 6, 0, 0, 0, 0, time.UTC)}, + } + + w := when.New(nil) + w.Add(en.Deadline(rules.Skip)) + + for _, f := range fixt { + res, err := w.Parse(f.text, ref) + require.Nil(t, err, f.text) + require.NotNil(t, res, f.text) + require.True(t, f.want.Equal(res.Time), "%s: got %s", f.text, res.Time) + } +} diff --git a/rules/en/past_time.go b/rules/en/past_time.go index 2d1e9f6..d4de753 100644 --- a/rules/en/past_time.go +++ b/rules/en/past_time.go @@ -68,7 +68,7 @@ func PastTime(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "month"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) - num) % 12) + c.Month = pointer.ToInt(int(ref.Month()) - num) } case strings.Contains(exponent, "year"): if c.Year == nil || overwrite { @@ -96,7 +96,7 @@ func PastTime(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "year"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) - 6) % 12) + c.Month = pointer.ToInt(int(ref.Month()) - 6) } } } diff --git a/rules/en/past_time_test.go b/rules/en/past_time_test.go index 9b547cf..1024e99 100644 --- a/rules/en/past_time_test.go +++ b/rules/en/past_time_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 TestPastTime(t *testing.T) { @@ -31,3 +32,25 @@ func TestPastTime(t *testing.T) { ApplyFixtures(t, "en.PastTime", w, fixt) } + +func TestPastTimeMonthsCrossYear(t *testing.T) { + ref := time.Date(2016, time.February, 6, 0, 0, 0, 0, time.UTC) + fixt := []struct { + text string + want time.Time + }{ + {"2 months ago", time.Date(2015, time.December, 6, 0, 0, 0, 0, time.UTC)}, + {"14 months ago", time.Date(2014, time.December, 6, 0, 0, 0, 0, time.UTC)}, + {"half a year ago", time.Date(2015, time.August, 6, 0, 0, 0, 0, time.UTC)}, + } + + w := when.New(nil) + w.Add(en.PastTime(rules.Skip)) + + for _, f := range fixt { + res, err := w.Parse(f.text, ref) + require.Nil(t, err, f.text) + require.NotNil(t, res, f.text) + require.True(t, f.want.Equal(res.Time), "%s: got %s", f.text, res.Time) + } +} diff --git a/rules/nl/deadline.go b/rules/nl/deadline.go index 6f2212c..8c1e809 100644 --- a/rules/nl/deadline.go +++ b/rules/nl/deadline.go @@ -67,7 +67,7 @@ func Deadline(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "maand"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) + num) % 12) + c.Month = pointer.ToInt(int(ref.Month()) + num) } case strings.Contains(exponent, "jaar"): if c.Year == nil || overwrite { @@ -95,7 +95,7 @@ func Deadline(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "jaar"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) + 6) % 12) + c.Month = pointer.ToInt(int(ref.Month()) + 6) } } } diff --git a/rules/nl/past_time.go b/rules/nl/past_time.go index 055d9c4..ff526ae 100644 --- a/rules/nl/past_time.go +++ b/rules/nl/past_time.go @@ -68,7 +68,7 @@ func PastTime(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "maand"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) - num) % 12) + c.Month = pointer.ToInt(int(ref.Month()) - num) } case strings.Contains(exponent, "jaar"): if c.Year == nil || overwrite { @@ -96,7 +96,7 @@ func PastTime(s rules.Strategy) rules.Rule { } case strings.Contains(exponent, "jaar"): if c.Month == nil || overwrite { - c.Month = pointer.ToInt((int(ref.Month()) - 6) % 12) + c.Month = pointer.ToInt(int(ref.Month()) - 6) } } } diff --git a/rules/ru/deadline.go b/rules/ru/deadline.go index 6cf6095..3613602 100644 --- a/rules/ru/deadline.go +++ b/rules/ru/deadline.go @@ -60,7 +60,7 @@ func Deadline(s rules.Strategy) rules.Rule { case strings.Contains(exponent, "недел"): c.Duration = time.Duration(num) * 7 * 24 * time.Hour case strings.Contains(exponent, "месяц"): - c.Month = pointer.ToInt((int(ref.Month()) + num) % 12) + c.Month = pointer.ToInt(int(ref.Month()) + num) case strings.Contains(exponent, "год") || strings.Contains(exponent, "лет"): c.Year = pointer.ToInt(ref.Year() + num) } @@ -76,7 +76,7 @@ func Deadline(s rules.Strategy) rules.Rule { // 2 weeks c.Duration = 14 * 24 * time.Hour case strings.Contains(exponent, "год") || strings.Contains(exponent, "лет"): - c.Month = pointer.ToInt((int(ref.Month()) + 6) % 12) + c.Month = pointer.ToInt(int(ref.Month()) + 6) } }