From 1b74d4a9520fc348a052bc1e1717901d58de1aab Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 30 Jan 2026 18:34:12 -0800 Subject: [PATCH 1/8] rewrite month diff --- sql/expression/function/time_math.go | 43 +++++++++++++++++++++------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index 325dfa66c8..a60d7a002c 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -689,19 +689,40 @@ 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 + 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 + 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 } } + + res = int64(yearDiff*12) + monthDiff + if negate { + res = -res + } 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)) From 0eea689e93ac79bd21bd4dc89f3a0f0a0bf4bcbf Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 30 Jan 2026 18:58:10 -0800 Subject: [PATCH 2/8] refactor month diff into reusable function for year and quarter --- sql/expression/function/time_math.go | 122 +++++++++------------------ 1 file changed, 38 insertions(+), 84 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index a60d7a002c..a13084ce00 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -672,6 +672,10 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) date1 := expr1.(time.Time) date2 := expr2.(time.Time) + if date1.Equal(date2) { + return 0, nil + } + diff := date2.Sub(date1) var res int64 @@ -689,95 +693,45 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) case "week": res = int64(diff.Hours() / (24 * 7)) case "month": - 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 - 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 - } - } - - res = int64(yearDiff*12) + monthDiff - if negate { - res = -res - } + res = monthDiff(date1, date2) 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 = monthDiff(date1, date2) / 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 = monthDiff(date1, date2) / 12 default: return nil, errors.NewKind("invalid interval unit: %s").New(unit) } return res, nil } + +func monthDiff(date1, date2 time.Time) int64 { + sign := 1 + before := date1 + after := date2 + if before.After(after) { + sign = -1 + before = date2 + after = date1 + } + + beforeYear, beforeMonth, beforeDay := before.Date() + afterYear, afterMonth, afterDay := after.Date() + yearDiff := afterYear - beforeYear + mDiff := int64(afterMonth) - int64(beforeMonth) + + if beforeDay > afterDay { + mDiff -= 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 { + mDiff -= 1 + } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() { + mDiff -= 1 + } + } + + return int64(sign) * (int64(yearDiff*12) + mDiff) +} From df431af90a1f62dbfc2e8c4523f4693ab1e25bcd Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 30 Jan 2026 19:16:21 -0800 Subject: [PATCH 3/8] add tests --- enginetest/queries/function_queries.go | 44 +++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index cd51f02db6..f18e5bf33f 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,48 @@ 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}}, + }, + // 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}}, + }, + // 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}}, + }, // TRIM Function Tests { Query: `SELECT TRIM(mytable.s) AS s FROM mytable`, From f301126b34df81556507e5b64b848dcef745b208 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 30 Jan 2026 19:40:24 -0800 Subject: [PATCH 4/8] add tests for timestamp diff between smallest and largest timestamps --- enginetest/queries/function_queries.go | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index f18e5bf33f..19525a2bad 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -940,6 +940,10 @@ var FunctionQueryTests = []QueryTest{ 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");`, @@ -961,6 +965,10 @@ var FunctionQueryTests = []QueryTest{ 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')", @@ -978,6 +986,52 @@ var FunctionQueryTests = []QueryTest{ 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`, From e0b9292d14b6d9af3cbd037b98098defc07f2ba9 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 30 Jan 2026 19:45:44 -0800 Subject: [PATCH 5/8] rename monthDiff to monthsDiff --- sql/expression/function/time_math.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index a13084ce00..38ee69039e 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -693,11 +693,11 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) case "week": res = int64(diff.Hours() / (24 * 7)) case "month": - res = monthDiff(date1, date2) + res = monthsDiff(date1, date2) case "quarter": - res = monthDiff(date1, date2) / 3 + res = monthsDiff(date1, date2) / 3 case "year": - res = monthDiff(date1, date2) / 12 + res = monthsDiff(date1, date2) / 12 default: return nil, errors.NewKind("invalid interval unit: %s").New(unit) } @@ -705,7 +705,7 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) return res, nil } -func monthDiff(date1, date2 time.Time) int64 { +func monthsDiff(date1, date2 time.Time) int64 { sign := 1 before := date1 after := date2 @@ -718,20 +718,20 @@ func monthDiff(date1, date2 time.Time) int64 { beforeYear, beforeMonth, beforeDay := before.Date() afterYear, afterMonth, afterDay := after.Date() yearDiff := afterYear - beforeYear - mDiff := int64(afterMonth) - int64(beforeMonth) + monthDiff := int64(afterMonth) - int64(beforeMonth) if beforeDay > afterDay { - mDiff -= 1 + 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 { - mDiff -= 1 + monthDiff -= 1 } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() { - mDiff -= 1 + monthDiff -= 1 } } - return int64(sign) * (int64(yearDiff*12) + mDiff) + return int64(sign) * (int64(yearDiff*12) + monthDiff) } From 3dab6b774be594563c9df0209b44ad1ffbc12490 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Sat, 31 Jan 2026 02:11:20 -0800 Subject: [PATCH 6/8] refactor dateDiff calculation to be more performant --- sql/expression/function/time_math.go | 60 ++++++++++++++++------------ 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index 38ee69039e..58618a7a1a 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -669,14 +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) - - if date1.Equal(date2) { - return 0, nil - } + time1 := expr1.(time.Time) + time2 := expr2.(time.Time) - diff := date2.Sub(date1) + diff := time2.Sub(time1) var res int64 switch unit { @@ -693,11 +689,11 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) case "week": res = int64(diff.Hours() / (24 * 7)) case "month": - res = monthsDiff(date1, date2) + res = dateDiff(time1, time2, true) case "quarter": - res = monthsDiff(date1, date2) / 3 + res = dateDiff(time1, time2, true) / 3 case "year": - res = monthsDiff(date1, date2) / 12 + res = dateDiff(time1, time2, false) default: return nil, errors.NewKind("invalid interval unit: %s").New(unit) } @@ -705,11 +701,18 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) return res, nil } -func monthsDiff(date1, date2 time.Time) int64 { +// dateDiff calculates the difference between two time.Times based on their Date and Clock values. If monthDiff is set +// to true, dateDiff returns the difference in number of full months. Otherwise, dateDiff returns the difference in +// number of full years. +func dateDiff(date1, date2 time.Time, monthDiff bool) int64 { + compare := date1.Compare(date2) + if compare == 0 { + return 0 + } sign := 1 before := date1 after := date2 - if before.After(after) { + if compare > 0 { sign = -1 before = date2 after = date1 @@ -717,21 +720,26 @@ func monthsDiff(date1, date2 time.Time) int64 { 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 + + res := int64(afterYear - beforeYear) + if monthDiff { + res = res*12 + int64(afterMonth) - int64(beforeMonth) + } + + if monthDiff || afterMonth == beforeMonth { + if beforeDay > afterDay { + res -= 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 { + res -= 1 + } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() { + res -= 1 + } } } - return int64(sign) * (int64(yearDiff*12) + monthDiff) + return int64(sign) * res } From 0fbede9e5b0b3acb3e086ceeb2b82cd83c7a86a2 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Sat, 31 Jan 2026 02:47:09 -0800 Subject: [PATCH 7/8] account for year differences where a whole year has not elapsed --- sql/expression/function/time_math.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index 58618a7a1a..b7290056b2 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -689,11 +689,11 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) case "week": res = int64(diff.Hours() / (24 * 7)) case "month": - res = dateDiff(time1, time2, true) + res = dateDiff(time1, time2, false) case "quarter": - res = dateDiff(time1, time2, true) / 3 + res = dateDiff(time1, time2, false) / 3 case "year": - res = dateDiff(time1, time2, false) + res = dateDiff(time1, time2, true) default: return nil, errors.NewKind("invalid interval unit: %s").New(unit) } @@ -701,10 +701,10 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) return res, nil } -// dateDiff calculates the difference between two time.Times based on their Date and Clock values. If monthDiff is set -// to true, dateDiff returns the difference in number of full months. Otherwise, dateDiff returns the difference in -// number of full years. -func dateDiff(date1, date2 time.Time, monthDiff bool) int64 { +// dateDiff calculates the difference between two time.Times based on their Date and Clock values. If yearDiff is set +// to true, dateDiff returns the difference in number of full years. Otherwise, dateDiff returns the difference in +// number of full months. +func dateDiff(date1, date2 time.Time, yearDiff bool) int64 { compare := date1.Compare(date2) if compare == 0 { return 0 @@ -721,12 +721,13 @@ func dateDiff(date1, date2 time.Time, monthDiff bool) int64 { beforeYear, beforeMonth, beforeDay := before.Date() afterYear, afterMonth, afterDay := after.Date() - res := int64(afterYear - beforeYear) - if monthDiff { - res = res*12 + int64(afterMonth) - int64(beforeMonth) + checkDayClock := !yearDiff || afterMonth == beforeMonth + res := int64(afterYear-beforeYear)*12 + int64(afterMonth) - int64(beforeMonth) + if yearDiff { + res = res / 12 } - if monthDiff || afterMonth == beforeMonth { + if checkDayClock { if beforeDay > afterDay { res -= 1 } else if beforeDay == afterDay { From a27201f6a88f8c86d6b2f4d69f38d54097b37cc2 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Mon, 2 Feb 2026 15:29:42 -0800 Subject: [PATCH 8/8] revert back to monthsDiff --- sql/expression/function/time_math.go | 61 ++++++++++++---------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index b7290056b2..ec3a7e4d32 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -689,11 +689,11 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) case "week": res = int64(diff.Hours() / (24 * 7)) case "month": - res = dateDiff(time1, time2, false) + res = monthsDiff(time1, time2) case "quarter": - res = dateDiff(time1, time2, false) / 3 + res = monthsDiff(time1, time2) / 3 case "year": - res = dateDiff(time1, time2, true) + res = monthsDiff(time1, time2) / 12 default: return nil, errors.NewKind("invalid interval unit: %s").New(unit) } @@ -701,46 +701,35 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) return res, nil } -// dateDiff calculates the difference between two time.Times based on their Date and Clock values. If yearDiff is set -// to true, dateDiff returns the difference in number of full years. Otherwise, dateDiff returns the difference in -// number of full months. -func dateDiff(date1, date2 time.Time, yearDiff bool) int64 { - compare := date1.Compare(date2) - if compare == 0 { - return 0 - } +// 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 := date1 - after := date2 - if compare > 0 { + before := time1 + after := time2 + if before.After(after) { sign = -1 - before = date2 - after = date1 + before = time2 + after = time1 } beforeYear, beforeMonth, beforeDay := before.Date() afterYear, afterMonth, afterDay := after.Date() - - checkDayClock := !yearDiff || afterMonth == beforeMonth - res := int64(afterYear-beforeYear)*12 + int64(afterMonth) - int64(beforeMonth) - if yearDiff { - res = res / 12 - } - - if checkDayClock { - if beforeDay > afterDay { - res -= 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 { - res -= 1 - } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() { - res -= 1 - } + 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) * res + return int64(sign) * (int64(yearDiff*12) + monthDiff) }