diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 19525a2bad..f9d82df9ec 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -990,47 +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 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. { - // 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, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{315564335999999999}}, }, { - // 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, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{315564335999}}, }, { - // 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, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{5259405599}}, }, { - // 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, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{87656759}}, }, { - // 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, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{3652364}}, }, { - // 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, '0000-03-01', '9999-12-31 23:59:59.999999');", + Expected: []sql.Row{{521766}}, }, // TRIM Function Tests { diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index ec3a7e4d32..75e9091186 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -672,28 +672,26 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) time1 := expr1.(time.Time) time2 := expr2.(time.Time) - diff := time2.Sub(time1) - var res int64 switch unit { case "microsecond": - res = diff.Microseconds() + res = microsecondsDiff(time1, time2) case "second": - res = int64(diff.Seconds()) + res = microsecondsDiff(time1, time2) / sql.MicrosecondsPerSecond case "minute": - res = int64(diff.Minutes()) + res = microsecondsDiff(time1, time2) / sql.MicrosecondsPerMinute case "hour": - res = int64(diff.Hours()) + res = microsecondsDiff(time1, time2) / sql.MicrosecondsPerHour case "day": - res = int64(diff.Hours() / 24) + res = microsecondsDiff(time1, time2) / sql.MicrosecondsPerDay case "week": - res = int64(diff.Hours() / (24 * 7)) + res = microsecondsDiff(time1, time2) / sql.MicrosecondsPerWeek case "month": res = monthsDiff(time1, time2) case "quarter": - res = monthsDiff(time1, time2) / 3 + res = monthsDiff(time1, time2) / sql.MonthsPerQuarter case "year": - res = monthsDiff(time1, time2) / 12 + res = monthsDiff(time1, time2) / sql.MonthsPerYear default: return nil, errors.NewKind("invalid interval unit: %s").New(unit) } @@ -723,7 +721,8 @@ func monthsDiff(time1, time2 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) + secondDiff := int64(afterHour-beforeHour)*sql.SecondsPerHour + int64(afterMin-beforeMin)*sql.SecondsPerMinute + + int64(afterSec-beforeSec) if secondDiff < 0 { monthDiff -= 1 } else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() { @@ -731,5 +730,9 @@ func monthsDiff(time1, time2 time.Time) int64 { } } - return int64(sign) * (int64(yearDiff*12) + monthDiff) + return int64(sign) * (int64(yearDiff*sql.MonthsPerYear) + monthDiff) +} + +func microsecondsDiff(date1, date2 time.Time) int64 { + return date2.UnixMicro() - date1.UnixMicro() } 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})$`)