From 683e83d4538580fa7a56268230871ef5aff5c1b2 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 30 Jan 2026 21:26:04 -0800 Subject: [PATCH 1/5] use microseconds from unix to calculate microseconds diff --- enginetest/queries/function_queries.go | 36 +++++++++----------------- sql/expression/function/time_math.go | 18 +++++++------ 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 19525a2bad..1f84438e40 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -992,45 +992,33 @@ var FunctionQueryTests = []QueryTest{ }, { // 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}}, + Query: "select timestampdiff(microsecond, '0001-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{315537897599999999}}, }, { // 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}}, + Query: "select timestampdiff(second, '0001-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{315537897599}}, }, { // 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}}, + Query: "select timestampdiff(minute, '0001-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{5258964959}}, }, { // 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}}, + Query: "select timestampdiff(hour, '0001-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{87649415}}, }, { // 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}}, + Query: "select timestampdiff(day, '0001-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{3652058}}, }, { // 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}}, + Query: "select timestampdiff(week, '0001-01-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{521722}}, }, // TRIM Function Tests { diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index 38ee69039e..757112acbc 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -676,22 +676,20 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) return 0, nil } - diff := date2.Sub(date1) - var res int64 switch unit { case "microsecond": - res = diff.Microseconds() + res = microsecondsDiff(date1, date2) case "second": - res = int64(diff.Seconds()) + res = microsecondsDiff(date1, date2) / 1000000 case "minute": - res = int64(diff.Minutes()) + res = microsecondsDiff(date1, date2) / (1000000 * 60) case "hour": - res = int64(diff.Hours()) + res = microsecondsDiff(date1, date2) / (1000000 * 60 * 60) case "day": - res = int64(diff.Hours() / 24) + res = microsecondsDiff(date1, date2) / (1000000 * 60 * 60 * 24) case "week": - res = int64(diff.Hours() / (24 * 7)) + res = microsecondsDiff(date1, date2) / (1000000 * 60 * 60 * 24 * 7) case "month": res = monthsDiff(date1, date2) case "quarter": @@ -735,3 +733,7 @@ func monthsDiff(date1, date2 time.Time) int64 { return int64(sign) * (int64(yearDiff*12) + monthDiff) } + +func microsecondsDiff(date1, date2 time.Time) int64 { + return date2.UnixMicro() - date1.UnixMicro() +} From 8e149a10c2375b44b1c790a0a0d62dfc4da09f1b Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 30 Jan 2026 23:49:30 -0800 Subject: [PATCH 2/5] update tests to use 0000-03-01 --- enginetest/queries/function_queries.go | 34 ++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 1f84438e40..1c9c73323f 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -990,35 +990,33 @@ var FunctionQueryTests = []QueryTest{ Query: "select timestampdiff(quarter, '0000-01-01', '9999-12-31 23:59:59.999999');", Expected: []sql.Row{{39999}}, }, + // TIMESTAMPDIFF tests for timestamp differences // https://github.com/dolthub/dolt/issues/10397 + // 0000-03-01 is used here instead of 0000-01-01 because 0000 is a leap year in Go (ie 0000-02-29 is a valid date), + // but not in MySQL. As a result, date differences between any day before 0000-02-29 and any after are off by one + // day. { - // https://github.com/dolthub/dolt/issues/10397 - Query: "select timestampdiff(microsecond, '0001-01-01', '9999-12-31 23:59:59.999999');", - Expected: []sql.Row{{315537897599999999}}, + Query: "select timestampdiff(microsecond, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{315564335999999999}}, }, { - // https://github.com/dolthub/dolt/issues/10397 - Query: "select timestampdiff(second, '0001-01-01', '9999-12-31 23:59:59.999999');", - Expected: []sql.Row{{315537897599}}, + Query: "select timestampdiff(second, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{315564335999}}, }, { - // https://github.com/dolthub/dolt/issues/10397 - Query: "select timestampdiff(minute, '0001-01-01', '9999-12-31 23:59:59.999999');", - Expected: []sql.Row{{5258964959}}, + Query: "select timestampdiff(minute, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{5259405599}}, }, { - // https://github.com/dolthub/dolt/issues/10397 - Query: "select timestampdiff(hour, '0001-01-01', '9999-12-31 23:59:59.999999');", - Expected: []sql.Row{{87649415}}, + Query: "select timestampdiff(hour, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{87656759}}, }, { - // https://github.com/dolthub/dolt/issues/10397 - Query: "select timestampdiff(day, '0001-01-01', '9999-12-31 23:59:59.999999');", - Expected: []sql.Row{{3652058}}, + Query: "select timestampdiff(day, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{3652364}}, }, { - // https://github.com/dolthub/dolt/issues/10397 - Query: "select timestampdiff(week, '0001-01-01', '9999-12-31 23:59:59.999999');", - Expected: []sql.Row{{521722}}, + Query: "select timestampdiff(week, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{521766}}, }, // TRIM Function Tests { From 2f9cb69a0926a18876e550fcacaf2c68dad5b876 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 30 Jan 2026 23:50:04 -0800 Subject: [PATCH 3/5] update comment --- enginetest/queries/function_queries.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 1c9c73323f..f9d82df9ec 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -990,7 +990,7 @@ var FunctionQueryTests = []QueryTest{ Query: "select timestampdiff(quarter, '0000-01-01', '9999-12-31 23:59:59.999999');", Expected: []sql.Row{{39999}}, }, - // TIMESTAMPDIFF tests for timestamp differences // https://github.com/dolthub/dolt/issues/10397 + // TIMESTAMPDIFF tests for large timestamp differences // https://github.com/dolthub/dolt/issues/10397 // 0000-03-01 is used here instead of 0000-01-01 because 0000 is a leap year in Go (ie 0000-02-29 is a valid date), // but not in MySQL. As a result, date differences between any day before 0000-02-29 and any after are off by one // day. From f8ea485e18ddfe1f59065136758c33bcd3065c28 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Sat, 31 Jan 2026 01:21:24 -0800 Subject: [PATCH 4/5] refactor datetime unit conversions into constants --- sql/expression/function/time_math.go | 21 +++++++++++---------- sql/time.go | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index 757112acbc..1a7649040a 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -681,21 +681,21 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) case "microsecond": res = microsecondsDiff(date1, date2) case "second": - res = microsecondsDiff(date1, date2) / 1000000 + res = microsecondsDiff(date1, date2) / sql.MicrosecondsPerSecond case "minute": - res = microsecondsDiff(date1, date2) / (1000000 * 60) + res = microsecondsDiff(date1, date2) / sql.MicrosecondsPerMinute case "hour": - res = microsecondsDiff(date1, date2) / (1000000 * 60 * 60) + res = microsecondsDiff(date1, date2) / sql.MicrosecondsPerHour case "day": - res = microsecondsDiff(date1, date2) / (1000000 * 60 * 60 * 24) + res = microsecondsDiff(date1, date2) / sql.MicrosecondsPerDay case "week": - res = microsecondsDiff(date1, date2) / (1000000 * 60 * 60 * 24 * 7) + res = microsecondsDiff(date1, date2) / sql.MicrosecondsPerWeek case "month": res = monthsDiff(date1, date2) case "quarter": - res = monthsDiff(date1, date2) / 3 + res = monthsDiff(date1, date2) / sql.MonthsPerQuarter case "year": - res = monthsDiff(date1, date2) / 12 + res = monthsDiff(date1, date2) / sql.MonthsPerYear default: return nil, errors.NewKind("invalid interval unit: %s").New(unit) } @@ -723,10 +723,11 @@ func monthsDiff(date1, date2 time.Time) int64 { } 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 { + secondsDiff := int64(afterHour-beforeHour)*sql.SecondsPerHour + + int64(afterMin-beforeMin)*sql.SecondsPerMinute + int64(afterSec-beforeSec) + if secondsDiff < 0 { monthDiff -= 1 - } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() { + } else if secondsDiff == 0 && before.Nanosecond() > after.Nanosecond() { monthDiff -= 1 } } diff --git a/sql/time.go b/sql/time.go index 704f884a08..57149afa21 100644 --- a/sql/time.go +++ b/sql/time.go @@ -23,6 +23,29 @@ import ( "time" ) +const ( + NanosecondsPerMicrosecond = int64(time.Microsecond) + + MicrosecondsPerSecond = int64(time.Second / time.Microsecond) + + SecondsPerMinute = int64(time.Second / time.Minute) + MicrosecondsPerMinute = int64(time.Minute / time.Microsecond) + + SecondsPerHour = int64(time.Hour / time.Second) + MicrosecondsPerHour = int64(time.Hour / time.Microsecond) + + hoursPerDay = 24 + DayDuration = hoursPerDay * time.Hour + MicrosecondsPerDay = MicrosecondsPerHour * hoursPerDay + + daysPerWeek = 7 + MicrosecondsPerWeek = MicrosecondsPerDay * daysPerWeek + + MonthsPerQuarter = 3 + + MonthsPerYear = 12 +) + // offsetRegex is a regex for matching MySQL offsets (e.g. +01:00). var offsetRegex = regexp.MustCompile(`(?m)^([+\-])(\d{1,2}):(\d{2})$`) From 5d2c2a05d9f35cd42c68c08792d1fc86500ebde4 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Mon, 2 Feb 2026 15:11:00 -0800 Subject: [PATCH 5/5] update to use constants --- sql/expression/function/time_math.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index 304766cd23..c2d40a24d3 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -689,7 +689,7 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) case "month": res = dateDiff(time1, time2, false) case "quarter": - res = dateDiff(time1, time2, false) / 3 + res = dateDiff(time1, time2, false) / sql.MonthsPerQuarter case "year": res = dateDiff(time1, time2, true) default: @@ -720,9 +720,9 @@ func dateDiff(date1, date2 time.Time, yearDiff bool) int64 { afterYear, afterMonth, afterDay := after.Date() checkDayClock := !yearDiff || afterMonth == beforeMonth - res := int64(afterYear-beforeYear)*12 + int64(afterMonth) - int64(beforeMonth) + res := int64(afterYear-beforeYear)*sql.MonthsPerYear + int64(afterMonth) - int64(beforeMonth) if yearDiff { - res = res / 12 + res = res / sql.MonthsPerYear } if checkDayClock { @@ -731,7 +731,8 @@ func dateDiff(date1, date2 time.Time, yearDiff bool) int64 { } else if beforeDay == afterDay { beforeHour, beforeMin, beforeSec := before.Clock() afterHour, afterMin, afterSec := after.Clock() - secondDiff := (afterHour-beforeHour)*3600 + (afterMin-beforeMin)*60 + (afterSec - beforeSec) + secondDiff := int64(afterHour-beforeHour)*sql.SecondsPerHour + + int64(afterMin-beforeMin)*sql.SecondsPerMinute + int64(afterSec-beforeSec) if secondDiff < 0 { res -= 1 } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() {