diff --git a/expression/builtin_time.go b/expression/builtin_time.go index 49a426b4d3fb9..bede57011b4c1 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -6948,6 +6948,92 @@ func (b *builtinTimestampAddSig) Clone() builtinFunc { return newSig } +<<<<<<< HEAD:expression/builtin_time.go +======= +var ( + minDatetimeInGoTime, _ = types.MinDatetime.GoTime(time.Local) + minDatetimeNanos = float64(minDatetimeInGoTime.Unix())*1e9 + float64(minDatetimeInGoTime.Nanosecond()) + maxDatetimeInGoTime, _ = types.MaxDatetime.GoTime(time.Local) + maxDatetimeNanos = float64(maxDatetimeInGoTime.Unix())*1e9 + float64(maxDatetimeInGoTime.Nanosecond()) + minDatetimeMonths = float64(types.MinDatetime.Year()*12 + types.MinDatetime.Month() - 1) // 0001-01-01 00:00:00 + maxDatetimeMonths = float64(types.MaxDatetime.Year()*12 + types.MaxDatetime.Month() - 1) // 9999-12-31 00:00:00 +) + +func validAddTime(nano1 float64, nano2 float64) bool { + return nano1+nano2 >= minDatetimeNanos && nano1+nano2 <= maxDatetimeNanos +} + +func validAddMonth(month1 float64, year, month int) bool { + tmp := month1 + float64(year)*12 + float64(month-1) + return tmp >= minDatetimeMonths && tmp <= maxDatetimeMonths +} + +func addUnitToTime(unit string, t time.Time, v float64) (time.Time, bool, error) { + s := math.Trunc(v * 1000000) + // round to the nearest int + v = math.Round(v) + var tb time.Time + nano := float64(t.Unix())*1e9 + float64(t.Nanosecond()) + switch unit { + case "MICROSECOND": + if !validAddTime(v*float64(time.Microsecond), nano) { + return tb, true, nil + } + tb = t.Add(time.Duration(v) * time.Microsecond) + case "SECOND": + if !validAddTime(s*float64(time.Microsecond), nano) { + return tb, true, nil + } + tb = t.Add(time.Duration(s) * time.Microsecond) + case "MINUTE": + if !validAddTime(v*float64(time.Minute), nano) { + return tb, true, nil + } + tb = t.Add(time.Duration(v) * time.Minute) + case "HOUR": + if !validAddTime(v*float64(time.Hour), nano) { + return tb, true, nil + } + tb = t.Add(time.Duration(v) * time.Hour) + case "DAY": + if !validAddTime(v*24*float64(time.Hour), nano) { + return tb, true, nil + } + tb = t.AddDate(0, 0, int(v)) + case "WEEK": + if !validAddTime(v*24*7*float64(time.Hour), nano) { + return tb, true, nil + } + tb = t.AddDate(0, 0, 7*int(v)) + case "MONTH": + if !validAddMonth(v, t.Year(), int(t.Month())) { + 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 + } + tb = t.AddDate(0, 3*int(v), 0) + case "YEAR": + if !validAddMonth(v*12, t.Year(), int(t.Month())) { + return tb, true, nil + } + tb = t.AddDate(int(v), 0, 0) + default: + return tb, false, types.ErrWrongValue.GenWithStackByArgs(types.TimeStr, unit) + } + return tb, false, nil +} + +>>>>>>> 536cf0068bc (expression: wrong result of timestampadd(month,1,date '2024-01-31') (#53101)):pkg/expression/builtin_time.go // evalString evals a builtinTimestampAddSig. // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_timestampadd func (b *builtinTimestampAddSig) evalString(row chunk.Row) (string, bool, error) { diff --git a/expression/builtin_time_test.go b/expression/builtin_time_test.go index eb60c07c44f28..d6b3836685995 100644 --- a/expression/builtin_time_test.go +++ b/expression/builtin_time_test.go @@ -2497,6 +2497,32 @@ func TestTimestampAdd(t *testing.T) { {"WEEK", 1, "2003-01-02 23:59:59", "2003-01-09 23:59:59"}, {"MICROSECOND", 1, 950501, "1995-05-01 00:00:00.000001"}, {"DAY", 28768, 0, ""}, +<<<<<<< HEAD:expression/builtin_time_test.go +======= + {"QUARTER", 3, "1995-05-01", "1996-02-01 00:00:00"}, + {"SECOND", 1.1, "1995-05-01", "1995-05-01 00:00:01.100000"}, + {"SECOND", -1, "1995-05-01", "1995-04-30 23:59:59"}, + {"SECOND", -1.1, "1995-05-01", "1995-04-30 23:59:58.900000"}, + {"SECOND", 9.9999e-6, "1995-05-01", "1995-05-01 00:00:00.000009"}, + {"SECOND", 9.9999e-7, "1995-05-01", "1995-05-01 00:00:00"}, + {"SECOND", -9.9999e-6, "1995-05-01", "1995-04-30 23:59:59.999991"}, + {"SECOND", -9.9999e-7, "1995-05-01", "1995-05-01 00:00:00"}, + {"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"}, +>>>>>>> 536cf0068bc (expression: wrong result of timestampadd(month,1,date '2024-01-31') (#53101)):pkg/expression/builtin_time_test.go } fc := funcs[ast.TimestampAdd]