diff --git a/expression/builtin_time.go b/expression/builtin_time.go index b60c6f5ebd150..ae0e1c185e0d2 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -6180,6 +6180,13 @@ func addUnitToTime(unit string, t time.Time, v float64) (time.Time, bool, error) return tb, true, nil } tb = t.AddDate(0, int(v), 0) + + // For corner case: timestampadd(month,1,date '2024-01-31') = "2024-02-29", timestampadd(month,1,date '2024-01-30') = "2024-02-29" + // `tb.Month()` refers to the actual result, `t.Month()+v` refers to the expect result. + // Actual result may be greater than expect result, we need to judge and modify it. + for int(tb.Month())%12 != (int(t.Month())+int(v))%12 { + tb = tb.AddDate(0, 0, -1) + } case "QUARTER": if !validAddMonth(v*3, t.Year(), int(t.Month())) { return tb, true, nil diff --git a/expression/builtin_time_test.go b/expression/builtin_time_test.go index 2010bf0a225b5..4b5579dfc1b2e 100644 --- a/expression/builtin_time_test.go +++ b/expression/builtin_time_test.go @@ -2525,6 +2525,18 @@ func TestTimestampAdd(t *testing.T) { {"MINUTE", 1.5, "1995-05-01 00:00:00", "1995-05-01 00:02:00"}, {"MINUTE", 1.5, "1995-05-01 00:00:00.000000", "1995-05-01 00:02:00"}, {"MICROSECOND", -100, "1995-05-01 00:00:00.0001", "1995-05-01 00:00:00"}, + + // issue41052 + {"MONTH", 1, "2024-01-31", "2024-02-29 00:00:00"}, + {"MONTH", 1, "2024-01-30", "2024-02-29 00:00:00"}, + {"MONTH", 1, "2024-01-29", "2024-02-29 00:00:00"}, + {"MONTH", 1, "2024-01-28", "2024-02-28 00:00:00"}, + {"MONTH", 1, "2024-10-31", "2024-11-30 00:00:00"}, + {"MONTH", 3, "2024-01-31", "2024-04-30 00:00:00"}, + {"MONTH", 15, "2024-01-31", "2025-04-30 00:00:00"}, + {"MONTH", 10, "2024-10-31", "2025-08-31 00:00:00"}, + {"MONTH", 1, "2024-11-30", "2024-12-30 00:00:00"}, + {"MONTH", 13, "2024-11-30", "2025-12-30 00:00:00"}, } fc := funcs[ast.TimestampAdd]