Skip to content

Commit b0e4117

Browse files
authored
Merge pull request #3407 from dolthub/angela/yeardiff
Calculate `year` for `timestampdiff` based on date and clock time values
2 parents 09b30ae + fb6eed2 commit b0e4117

File tree

3 files changed

+63
-19
lines changed

3 files changed

+63
-19
lines changed

enginetest/queries/function_queries.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -911,7 +911,31 @@ var FunctionQueryTests = []QueryTest{
911911
Query: "SELECT TIMESTAMPDIFF(SECOND, null, '2007-12-31 00:00:00');",
912912
Expected: []sql.Row{{nil}},
913913
},
914-
914+
// https://github.com/dolthub/dolt/issues/10393
915+
{
916+
Query: "SELECT TIMESTAMPDIFF(YEAR, DATE '2011-07-05', DATE '2026-07-04')",
917+
Expected: []sql.Row{{14}},
918+
},
919+
{
920+
Query: "SELECT TIMESTAMPDIFF(YEAR, DATE '2026-07-04', DATE '2011-07-05')",
921+
Expected: []sql.Row{{-14}},
922+
},
923+
{
924+
Query: "SELECT TIMESTAMPDIFF(YEAR, DATE '2026-07-05', DATE '2026-07-04')",
925+
Expected: []sql.Row{{0}},
926+
},
927+
{
928+
Query: "SELECT TIMESTAMPDIFF(YEAR, DATE '2026-07-04', DATE '2026-07-05')",
929+
Expected: []sql.Row{{0}},
930+
},
931+
{
932+
Query: "SELECT TIMESTAMPDIFF(YEAR, DATE '2025-07-04', DATE '2026-07-03')",
933+
Expected: []sql.Row{{0}},
934+
},
935+
{
936+
Query: "SELECT TIMESTAMPDIFF(YEAR, DATE '2026-07-03', DATE '2025-07-04')",
937+
Expected: []sql.Row{{0}},
938+
},
915939
// TRIM Function Tests
916940
{
917941
Query: `SELECT TRIM(mytable.s) AS s FROM mytable`,

sql/expression/function/time_math.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,7 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error)
689689
case "week":
690690
res = int64(diff.Hours() / (24 * 7))
691691
case "month":
692+
// TODO: this calculation is not correct. Not every month is 30 days https://github.com/dolthub/dolt/issues/10393
692693
res = int64(diff.Hours() / (24 * 30))
693694
if res > 0 {
694695
if date2.Day()-date1.Day() < 0 {
@@ -702,6 +703,7 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error)
702703
}
703704
}
704705
case "quarter":
706+
// TODO: this calculation is not correct. Not every month is 30 days https://github.com/dolthub/dolt/issues/10393
705707
monthRes := int64(diff.Hours() / (24 * 30))
706708
if monthRes > 0 {
707709
if date2.Day()-date1.Day() < 0 {
@@ -716,25 +718,42 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error)
716718
}
717719
res = monthRes / 3
718720
case "year":
719-
yearRes := int64(diff.Hours() / (24 * 365))
720-
if yearRes > 0 {
721-
monthRes := int64(diff.Hours() / (24 * 30))
722-
if monthRes > 0 {
723-
if date2.Day()-date1.Day() < 0 {
724-
monthRes -= 1
725-
} else if date2.Hour()-date1.Hour() < 0 {
726-
monthRes -= 1
727-
} else if date2.Minute()-date1.Minute() < 0 {
728-
monthRes -= 1
729-
} else if date2.Second()-date1.Second() < 0 {
730-
monthRes -= 1
721+
if diff == 0 {
722+
return 0, nil
723+
}
724+
negate := false
725+
before := date1
726+
after := date2
727+
if diff < 0 {
728+
negate = true
729+
before = date2
730+
after = date1
731+
}
732+
733+
beforeYear, beforeMonth, beforeDay := before.Date()
734+
afterYear, afterMonth, afterDay := after.Date()
735+
yearDiff := afterYear - beforeYear
736+
if beforeMonth > afterMonth {
737+
yearDiff -= 1
738+
} else if beforeMonth == afterMonth {
739+
if beforeDay > afterDay {
740+
yearDiff -= 1
741+
} else if beforeDay == afterDay {
742+
beforeHour, beforeMin, beforeSec := before.Clock()
743+
afterHour, afterMin, afterSec := after.Clock()
744+
secondDiff := (afterHour-beforeHour)*3600 + (afterMin-beforeMin)*60 + (afterSec - beforeSec)
745+
if secondDiff < 0 {
746+
yearDiff -= 1
747+
} else if secondDiff == 0 && before.Nanosecond() > after.Nanosecond() {
748+
yearDiff -= 1
731749
}
732750
}
733-
res = monthRes / 12
734-
} else {
735-
res = yearRes
736751
}
737752

753+
res = int64(yearDiff)
754+
if negate {
755+
res = -res
756+
}
738757
default:
739758
return nil, errors.NewKind("invalid interval unit: %s").New(unit)
740759
}

sql/expression/function/time_math_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -652,9 +652,10 @@ func TestTimestampDiff(t *testing.T) {
652652
{"quarter - second less than a quarter", types.Text, types.Text, types.Text, sql.NewRow("SQL_TSI_QUARTER", "2007-08-30 00:00:01", "2007-11-30 00:00:00"), int64(0), false},
653653
{"quarter", types.Text, types.Text, types.Text, sql.NewRow("QUARTER", "2006-08-30 00:00:00", "2007-11-30 00:00:00"), int64(5), false},
654654
{"quarter - negative", types.Text, types.Text, types.Text, sql.NewRow("QUARTER", "2006-08-30 00:00:00", "2002-11-30 00:00:00"), int64(-15), false},
655-
{"year - second less than a month", types.Text, types.Text, types.Text, sql.NewRow("YEAR", "2019-01-01 00:00:00", "2019-12-31 23:59:59"), int64(0), false},
656-
{"year", types.Text, types.Text, types.Text, sql.NewRow("YEAR", "2016-09-04 00:00:01", "2021-09-04 00:00:00"), int64(4), false},
657-
{"year - ", types.Text, types.Text, types.Text, sql.NewRow("YEAR", "2016-09-04 01:00:01", "2021-09-04 02:00:02"), int64(5), false},
655+
{"year - same year", types.Text, types.Text, types.Text, sql.NewRow("YEAR", "2019-01-01 00:00:00", "2019-12-31 23:59:59"), int64(0), false},
656+
{"year - one second less than five years", types.Text, types.Text, types.Text, sql.NewRow("YEAR", "2016-09-04 00:00:01", "2021-09-04 00:00:00"), int64(4), false},
657+
{"year - one hour more than five years", types.Text, types.Text, types.Text, sql.NewRow("YEAR", "2016-09-04 01:00:01", "2021-09-04 02:00:02"), int64(5), false},
658+
{"year - one microsecond less than five years", types.Text, types.Text, types.Text, sql.NewRow("YEAR", "2016-09-04 01:00:02.000001", "2021-09-04 01:00:02"), int64(4), false},
658659
{"year - negative", types.Text, types.Text, types.Text, sql.NewRow("SQL_TSI_YEAR", "2016-09-05 00:00:00", "2006-09-04 23:59:59"), int64(-10), false},
659660
{"unit is null", types.Text, types.Text, types.Text, sql.NewRow(nil, "2016-09-05 00:00:00", "2006-09-04 23:59:59"), nil, true},
660661
{"first timestamp is null", types.Text, types.Text, types.Text, sql.NewRow("YEAR", nil, "2021-09-04 02:00:02"), nil, false},

0 commit comments

Comments
 (0)