diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index cd51f02db6..19525a2bad 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -911,7 +911,7 @@ var FunctionQueryTests = []QueryTest{ Query: "SELECT TIMESTAMPDIFF(SECOND, null, '2007-12-31 00:00:00');", Expected: []sql.Row{{nil}}, }, - // https://github.com/dolthub/dolt/issues/10393 + // TIMESTAMPDIFF YEAR tests https://github.com/dolthub/dolt/issues/10393 { Query: "SELECT TIMESTAMPDIFF(YEAR, DATE '2011-07-05', DATE '2026-07-04')", Expected: []sql.Row{{14}}, @@ -936,6 +936,102 @@ var FunctionQueryTests = []QueryTest{ Query: "SELECT TIMESTAMPDIFF(YEAR, DATE '2026-07-03', DATE '2025-07-04')", Expected: []sql.Row{{0}}, }, + { + Query: `select timestampdiff(year, "0050-01-01", "2020-01-01");`, + Expected: []sql.Row{{1970}}, + }, + { + Query: "select timestampdiff(year, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{9999}}, + }, + // TIMESTAMPDIFF MONTH tests https://github.com/dolthub/dolt/issues/10393 + { + Query: `select timestampdiff(year, "2000-12-25", "2020-2-20");`, + Expected: []sql.Row{{19}}, + }, + { + Query: "SELECT TIMESTAMPDIFF(month, DATE '2011-07-05', DATE '2026-07-04')", + Expected: []sql.Row{{179}}, + }, + { + Query: "SELECT TIMESTAMPDIFF(month, DATE '2026-07-04', DATE '2011-07-05')", + Expected: []sql.Row{{-179}}, + }, + { + Query: `select timestampdiff(month, "2000-12-25", "2020-2-20");`, + Expected: []sql.Row{{229}}, + }, + { + Query: `select timestampdiff(month, "0050-01-01", "2020-01-01");`, + Expected: []sql.Row{{23640}}, + }, + { + Query: "select timestampdiff(month, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{119999}}, + }, + // TIMESTAMPDIFF QUARTER tests https://github.com/dolthub/dolt/issues/10393 + { + Query: "SELECT TIMESTAMPDIFF(quarter, DATE '2011-07-05', DATE '2026-07-04')", + Expected: []sql.Row{{59}}, + }, + { + Query: "SELECT TIMESTAMPDIFF(quarter, DATE '2026-07-04', DATE '2011-07-05')", + Expected: []sql.Row{{-59}}, + }, + { + Query: `select timestampdiff(quarter, "0050-01-01", "2020-01-01");`, + Expected: []sql.Row{{7880}}, + }, + { + Query: `select timestampdiff(quarter, "2000-12-25", "2020-2-20");`, + Expected: []sql.Row{{76}}, + }, + { + Query: "select timestampdiff(quarter, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{39999}}, + }, + { + // https://github.com/dolthub/dolt/issues/10397 + Skip: true, + // might need to change first date to 0001-01-01 since 0000 is a leap year in Go but not in MySQL + Query: "select timestampdiff(microsecond, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{315569433599999999}}, + }, + { + // https://github.com/dolthub/dolt/issues/10397 + Skip: true, + // might need to change first date to 0001-01-01 since 0000 is a leap year in Go but not in MySQL + Query: "select timestampdiff(second, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{315569433599}}, + }, + { + // https://github.com/dolthub/dolt/issues/10397 + Skip: true, + // might need to change first date to 0001-01-01 since 0000 is a leap year in Go but not in MySQL + Query: "select timestampdiff(minute, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{5259490559}}, + }, + { + // https://github.com/dolthub/dolt/issues/10397 + Skip: true, + // might need to change first date to 0001-01-01 since 0000 is a leap year in Go but not in MySQL + Query: "select timestampdiff(hour, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{87658175}}, + }, + { + // https://github.com/dolthub/dolt/issues/10397 + Skip: true, + // might need to change first date to 0001-01-01 since 0000 is a leap year in Go but not in MySQL + Query: "select timestampdiff(day, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{3652423}}, + }, + { + // https://github.com/dolthub/dolt/issues/10397 + Skip: true, + // might need to change first date to 0001-01-01 since 0000 is a leap year in Go but not in MySQL + Query: "select timestampdiff(week, '0000-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{521774}}, + }, // TRIM Function Tests { Query: `SELECT TRIM(mytable.s) AS s FROM mytable`, diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index 325dfa66c8..ec3a7e4d32 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -669,10 +669,10 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) unit = strings.TrimPrefix(strings.ToLower(unit.(string)), "sql_tsi_") - date1 := expr1.(time.Time) - date2 := expr2.(time.Time) + time1 := expr1.(time.Time) + time2 := expr2.(time.Time) - diff := date2.Sub(date1) + diff := time2.Sub(time1) var res int64 switch unit { @@ -689,74 +689,47 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) case "week": res = int64(diff.Hours() / (24 * 7)) case "month": - // TODO: this calculation is not correct. Not every month is 30 days https://github.com/dolthub/dolt/issues/10393 - res = int64(diff.Hours() / (24 * 30)) - if res > 0 { - if date2.Day()-date1.Day() < 0 { - res -= 1 - } else if date2.Hour()-date1.Hour() < 0 { - res -= 1 - } else if date2.Minute()-date1.Minute() < 0 { - res -= 1 - } else if date2.Second()-date1.Second() < 0 { - res -= 1 - } - } + res = monthsDiff(time1, time2) case "quarter": - // TODO: this calculation is not correct. Not every month is 30 days https://github.com/dolthub/dolt/issues/10393 - monthRes := int64(diff.Hours() / (24 * 30)) - if monthRes > 0 { - if date2.Day()-date1.Day() < 0 { - monthRes -= 1 - } else if date2.Hour()-date1.Hour() < 0 { - monthRes -= 1 - } else if date2.Minute()-date1.Minute() < 0 { - monthRes -= 1 - } else if date2.Second()-date1.Second() < 0 { - monthRes -= 1 - } - } - res = monthRes / 3 + res = monthsDiff(time1, time2) / 3 case "year": - if diff == 0 { - return 0, nil - } - negate := false - before := date1 - after := date2 - if diff < 0 { - negate = true - before = date2 - after = date1 - } - - beforeYear, beforeMonth, beforeDay := before.Date() - afterYear, afterMonth, afterDay := after.Date() - yearDiff := afterYear - beforeYear - if beforeMonth > afterMonth { - yearDiff -= 1 - } else if beforeMonth == afterMonth { - if beforeDay > afterDay { - yearDiff -= 1 - } else if beforeDay == afterDay { - beforeHour, beforeMin, beforeSec := before.Clock() - afterHour, afterMin, afterSec := after.Clock() - secondDiff := (afterHour-beforeHour)*3600 + (afterMin-beforeMin)*60 + (afterSec - beforeSec) - if secondDiff < 0 { - yearDiff -= 1 - } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() { - yearDiff -= 1 - } - } - } - - res = int64(yearDiff) - if negate { - res = -res - } + res = monthsDiff(time1, time2) / 12 default: return nil, errors.NewKind("invalid interval unit: %s").New(unit) } return res, nil } + +// monthsDiff calculates the difference between two time.Times in number of full months based on their Date and Clock +// values. +func monthsDiff(time1, time2 time.Time) int64 { + sign := 1 + before := time1 + after := time2 + if before.After(after) { + sign = -1 + before = time2 + after = time1 + } + + beforeYear, beforeMonth, beforeDay := before.Date() + afterYear, afterMonth, afterDay := after.Date() + yearDiff := afterYear - beforeYear + monthDiff := int64(afterMonth) - int64(beforeMonth) + + if beforeDay > afterDay { + monthDiff -= 1 + } else if beforeDay == afterDay { + beforeHour, beforeMin, beforeSec := before.Clock() + afterHour, afterMin, afterSec := after.Clock() + secondDiff := (afterHour-beforeHour)*3600 + (afterMin-beforeMin)*60 + (afterSec - beforeSec) + if secondDiff < 0 { + monthDiff -= 1 + } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() { + monthDiff -= 1 + } + } + + return int64(sign) * (int64(yearDiff*12) + monthDiff) +}