From e456e48f0935cdd256391600a60e881510596afc Mon Sep 17 00:00:00 2001
From: Noble Mittal <noblemittal@outlook.com>
Date: Sun, 28 Jan 2024 02:28:05 +0530
Subject: [PATCH 1/8] eval: Implement FROM_DAYS

Signed-off-by: Noble Mittal <noblemittal@outlook.com>
---
 go/vt/vtgate/evalengine/cached_size.go       | 12 +++++
 go/vt/vtgate/evalengine/compiler_asm.go      | 17 +++++++
 go/vt/vtgate/evalengine/fn_time.go           | 52 ++++++++++++++++++++
 go/vt/vtgate/evalengine/testcases/cases.go   | 20 ++++++++
 go/vt/vtgate/evalengine/translate_builtin.go |  5 ++
 5 files changed, 106 insertions(+)

diff --git a/go/vt/vtgate/evalengine/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go
index 077fcf0c2da..42ab2daeb17 100644
--- a/go/vt/vtgate/evalengine/cached_size.go
+++ b/go/vt/vtgate/evalengine/cached_size.go
@@ -835,6 +835,18 @@ func (cached *builtinFromBase64) CachedSize(alloc bool) int64 {
 	size += cached.CallExpr.CachedSize(false)
 	return size
 }
+func (cached *builtinFromDays) CachedSize(alloc bool) int64 {
+	if cached == nil {
+		return int64(0)
+	}
+	size := int64(0)
+	if alloc {
+		size += int64(48)
+	}
+	// field CallExpr vitess.io/vitess/go/vt/vtgate/evalengine.CallExpr
+	size += cached.CallExpr.CachedSize(false)
+	return size
+}
 func (cached *builtinFromUnixtime) CachedSize(alloc bool) int64 {
 	if cached == nil {
 		return int64(0)
diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go
index 1a750ed7d0e..a162b4a3468 100644
--- a/go/vt/vtgate/evalengine/compiler_asm.go
+++ b/go/vt/vtgate/evalengine/compiler_asm.go
@@ -3795,6 +3795,23 @@ func (asm *assembler) Fn_LAST_DAY() {
 	}, "FN LAST_DAY DATETIME(SP-1)")
 }
 
+func (asm *assembler) Fn_FROM_DAYS() {
+	asm.emit(func(env *ExpressionEnv) int {
+		arg := env.vm.stack[env.vm.sp-1].(*evalInt64)
+		if arg == nil {
+			return 1
+		}
+
+		d := fromDays(env.currentTimezone(), arg.i)
+		if d == nil || d.IsZero() {
+			env.vm.stack[env.vm.sp-1] = nil
+		} else {
+			env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(*d)
+		}
+		return 1
+	}, "FN FROM_DAYS INT64(SP-1)")
+}
+
 func (asm *assembler) Fn_QUARTER() {
 	asm.emit(func(env *ExpressionEnv) int {
 		if env.vm.stack[env.vm.sp-1] == nil {
diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go
index de1b7259aff..24697ce0dbe 100644
--- a/go/vt/vtgate/evalengine/fn_time.go
+++ b/go/vt/vtgate/evalengine/fn_time.go
@@ -111,6 +111,10 @@ type (
 		CallExpr
 	}
 
+	builtinFromDays struct {
+		CallExpr
+	}
+
 	builtinQuarter struct {
 		CallExpr
 	}
@@ -173,6 +177,7 @@ var _ IR = (*builtinMinute)(nil)
 var _ IR = (*builtinMonth)(nil)
 var _ IR = (*builtinMonthName)(nil)
 var _ IR = (*builtinLastDay)(nil)
+var _ IR = (*builtinFromDays)(nil)
 var _ IR = (*builtinQuarter)(nil)
 var _ IR = (*builtinSecond)(nil)
 var _ IR = (*builtinTime)(nil)
@@ -1249,6 +1254,53 @@ func (call *builtinLastDay) compile(c *compiler) (ctype, error) {
 	return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil
 }
 
+func fromDays(loc *time.Location, d int64) *datetime.Date {
+	// 3652424 days corresponds to maximum date i.e. 9999-12-31
+	// mysql returns 0000-00-00 for days below 366
+	if d > 3652424 || d < 366 {
+		return nil
+	}
+
+	t := time.Date(1, time.January, 1, 0, 0, 0, 0, loc).AddDate(0, 0, int(d-366))
+	dt := datetime.NewDateFromStd(t)
+	return &dt
+}
+
+func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) {
+	d, err := b.arg1(env)
+	if err != nil {
+		return nil, err
+	}
+	if d == nil {
+		return nil, nil
+	}
+	days := evalToInt64(d).i
+	dt := fromDays(env.currentTimezone(), days)
+
+	if dt == nil {
+		return nil, nil
+	}
+	return newEvalDate(*dt, env.sqlmode.AllowZeroDate()), nil
+}
+
+func (call *builtinFromDays) compile(c *compiler) (ctype, error) {
+	arg, err := call.Arguments[0].compile(c)
+	if err != nil {
+		return ctype{}, err
+	}
+
+	skip := c.compileNullCheck1(arg)
+	switch arg.Type {
+	case sqltypes.Int64:
+	default:
+		c.asm.Convert_xi(1)
+	}
+
+	c.asm.Fn_FROM_DAYS()
+	c.asm.jumpDestination(skip)
+	return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil
+}
+
 func (b *builtinQuarter) eval(env *ExpressionEnv) (eval, error) {
 	date, err := b.arg1(env)
 	if err != nil {
diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go
index 59ba6591a6d..47d097f345c 100644
--- a/go/vt/vtgate/evalengine/testcases/cases.go
+++ b/go/vt/vtgate/evalengine/testcases/cases.go
@@ -131,6 +131,7 @@ var Cases = []TestCase{
 	{Run: FnMonth},
 	{Run: FnMonthName},
 	{Run: FnLastDay},
+	{Run: FnFromDays},
 	{Run: FnQuarter},
 	{Run: FnSecond},
 	{Run: FnTime},
@@ -1766,6 +1767,25 @@ func FnLastDay(yield Query) {
 	}
 }
 
+func FnFromDays(yield Query) {
+	for _, d := range inputConversions {
+		yield(fmt.Sprintf("FROM_DAYS(%s)", d), nil)
+	}
+
+	days := []string{
+		"0",
+		"1",
+		"366",
+		"365242",
+		"3652424",
+		"3652425",
+	}
+
+	for _, d := range days {
+		yield(fmt.Sprintf("FROM_DAYS(%s)", d), nil)
+	}
+}
+
 func FnQuarter(yield Query) {
 	for _, d := range inputConversions {
 		yield(fmt.Sprintf("QUARTER(%s)", d), nil)
diff --git a/go/vt/vtgate/evalengine/translate_builtin.go b/go/vt/vtgate/evalengine/translate_builtin.go
index 1546a6e2610..250205a26d2 100644
--- a/go/vt/vtgate/evalengine/translate_builtin.go
+++ b/go/vt/vtgate/evalengine/translate_builtin.go
@@ -419,6 +419,11 @@ func (ast *astCompiler) translateFuncExpr(fn *sqlparser.FuncExpr) (IR, error) {
 			return nil, argError(method)
 		}
 		return &builtinLastDay{CallExpr: call}, nil
+	case "from_days":
+		if len(args) != 1 {
+			return nil, argError(method)
+		}
+		return &builtinFromDays{CallExpr: call}, nil
 	case "quarter":
 		if len(args) != 1 {
 			return nil, argError(method)

From d8453079b59f2f179c0446a96f3caf09310f04d9 Mon Sep 17 00:00:00 2001
From: Noble Mittal <noblemittal@outlook.com>
Date: Sun, 28 Jan 2024 04:36:23 +0530
Subject: [PATCH 2/8] Rename mysqlDateFromDayNumber to export

Signed-off-by: Noble Mittal <noblemittal@outlook.com>
---
 go/mysql/datetime/datetime.go    | 4 ++--
 go/mysql/datetime/mydate.go      | 8 ++++----
 go/mysql/datetime/mydate_test.go | 2 +-
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go
index 67191e5c48e..1bd5d9ce39d 100644
--- a/go/mysql/datetime/datetime.go
+++ b/go/mysql/datetime/datetime.go
@@ -615,13 +615,13 @@ func (dt *DateTime) addInterval(itv *Interval) bool {
 			return false
 		}
 
-		dt.Date.year, dt.Date.month, dt.Date.day = mysqlDateFromDayNumber(daynum)
+		dt.Date.year, dt.Date.month, dt.Date.day = MysqlDateFromDayNumber(daynum)
 		return true
 
 	case itv.unit.HasDayParts():
 		daynum := mysqlDayNumber(dt.Date.Year(), dt.Date.Month(), dt.Date.Day())
 		daynum += itv.day
-		dt.Date.year, dt.Date.month, dt.Date.day = mysqlDateFromDayNumber(daynum)
+		dt.Date.year, dt.Date.month, dt.Date.day = MysqlDateFromDayNumber(daynum)
 		return true
 
 	case itv.unit.HasMonthParts():
diff --git a/go/mysql/datetime/mydate.go b/go/mysql/datetime/mydate.go
index 1d4a2eaf958..c172ff7de6f 100644
--- a/go/mysql/datetime/mydate.go
+++ b/go/mysql/datetime/mydate.go
@@ -23,8 +23,8 @@ package datetime
 // day count that traditional datetime systems use (e.g. the daycount
 // algorithm in the Go standard library). It is often off by one, possibly
 // because of incorrect leap year handling, but the inverse of the algorithm
-// in mysqlDateFromDayNumber takes this into account. Hence, the results
-// of this function can ONLY be passed to mysqlDateFromDayNumber; using
+// in MysqlDateFromDayNumber takes this into account. Hence, the results
+// of this function can ONLY be passed to MysqlDateFromDayNumber; using
 // a day number with one of Go's datetime APIs will return incorrect results.
 // This API should only be used when performing datetime calculations (addition
 // and subtraction), so that the results match MySQL's. All other date handling
@@ -46,7 +46,7 @@ func mysqlDayNumber(year, month, day int) int {
 	return days + year/4 - leapAdjust
 }
 
-// mysqlDateFromDayNumber converts an absolute day number into a date (a year, month, day triplet).
+// MysqlDateFromDayNumber converts an absolute day number into a date (a year, month, day triplet).
 // This is an algorithm that has been reverse engineered from MySQL;
 // the tables used as a reference can be found in `testdata/daynr_to_date.json`.
 // See the warning from mysqlDayNumber: the day number used as an argument to
@@ -54,7 +54,7 @@ func mysqlDayNumber(year, month, day int) int {
 // This API should only be used when performing datetime calculations (addition
 // and subtraction), so that the results match MySQL's. All other date handling
 // operations must use our helpers based on Go's standard library.
-func mysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) {
+func MysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) {
 	if daynr <= 365 || daynr >= 3652500 {
 		return 0, 0, 0
 	}
diff --git a/go/mysql/datetime/mydate_test.go b/go/mysql/datetime/mydate_test.go
index 29ecd2df9d2..d3662df9c59 100644
--- a/go/mysql/datetime/mydate_test.go
+++ b/go/mysql/datetime/mydate_test.go
@@ -49,7 +49,7 @@ func TestDayNumberFields(t *testing.T) {
 	require.NoError(t, err)
 
 	for _, tc := range expected {
-		y, m, d := mysqlDateFromDayNumber(tc[0])
+		y, m, d := MysqlDateFromDayNumber(tc[0])
 		assert.Equal(t, tc[1], int(y))
 		assert.Equal(t, tc[2], int(m))
 		assert.Equal(t, tc[3], int(d))

From eb184fee01e982945cf524ffe8efeb882e0a6ca9 Mon Sep 17 00:00:00 2001
From: Noble Mittal <noblemittal@outlook.com>
Date: Sun, 28 Jan 2024 04:38:53 +0530
Subject: [PATCH 3/8] Make FROM_DAYS return zero date

Signed-off-by: Noble Mittal <noblemittal@outlook.com>
---
 go/vt/vtgate/evalengine/compiler_asm.go    | 11 ++++++--
 go/vt/vtgate/evalengine/fn_time.go         | 33 +++++++++-------------
 go/vt/vtgate/evalengine/testcases/cases.go |  1 +
 3 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go
index a162b4a3468..c14bed15f33 100644
--- a/go/vt/vtgate/evalengine/compiler_asm.go
+++ b/go/vt/vtgate/evalengine/compiler_asm.go
@@ -37,6 +37,7 @@ import (
 
 	"vitess.io/vitess/go/mysql/collations/charset/types"
 	"vitess.io/vitess/go/mysql/collations/colldata"
+	mysqldt "vitess.io/vitess/go/mysql/datetime"
 
 	"vitess.io/vitess/go/hack"
 	"vitess.io/vitess/go/mysql/collations"
@@ -3799,14 +3800,18 @@ func (asm *assembler) Fn_FROM_DAYS() {
 	asm.emit(func(env *ExpressionEnv) int {
 		arg := env.vm.stack[env.vm.sp-1].(*evalInt64)
 		if arg == nil {
+			env.vm.stack[env.vm.sp-1] = nil
 			return 1
 		}
 
-		d := fromDays(env.currentTimezone(), arg.i)
-		if d == nil || d.IsZero() {
+		y, m, d := mysqldt.MysqlDateFromDayNumber(int(arg.i))
+		if y == 0 && m == 0 && d == 0 {
+			env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(datetime.Date{})
+		} else if y > 9999 {
 			env.vm.stack[env.vm.sp-1] = nil
 		} else {
-			env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(*d)
+			dt := datetime.NewDateFromStd(time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, env.currentTimezone()))
+			env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(dt)
 		}
 		return 1
 	}, "FN FROM_DAYS INT64(SP-1)")
diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go
index 24697ce0dbe..6eb8db8c721 100644
--- a/go/vt/vtgate/evalengine/fn_time.go
+++ b/go/vt/vtgate/evalengine/fn_time.go
@@ -23,6 +23,7 @@ import (
 	"vitess.io/vitess/go/hack"
 	"vitess.io/vitess/go/mysql/collations"
 	"vitess.io/vitess/go/mysql/datetime"
+	mysqldt "vitess.io/vitess/go/mysql/datetime"
 	"vitess.io/vitess/go/mysql/decimal"
 	"vitess.io/vitess/go/sqltypes"
 )
@@ -1254,33 +1255,27 @@ func (call *builtinLastDay) compile(c *compiler) (ctype, error) {
 	return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil
 }
 
-func fromDays(loc *time.Location, d int64) *datetime.Date {
-	// 3652424 days corresponds to maximum date i.e. 9999-12-31
-	// mysql returns 0000-00-00 for days below 366
-	if d > 3652424 || d < 366 {
-		return nil
+func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) {
+	arg, err := b.arg1(env)
+	if arg == nil || err != nil {
+		return nil, nil
 	}
 
-	t := time.Date(1, time.January, 1, 0, 0, 0, 0, loc).AddDate(0, 0, int(d-366))
-	dt := datetime.NewDateFromStd(t)
-	return &dt
-}
+	days := evalToInt64(arg).i
+	y, m, d := mysqldt.MysqlDateFromDayNumber(int(days))
 
-func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) {
-	d, err := b.arg1(env)
-	if err != nil {
-		return nil, err
-	}
-	if d == nil {
+	// mysql returns 0000-00-00 for days below 366 and above 3652499
+	if y == 0 && m == 0 && d == 0 {
 		return nil, nil
 	}
-	days := evalToInt64(d).i
-	dt := fromDays(env.currentTimezone(), days)
 
-	if dt == nil {
+	// mysql returns NULL if y is greater than 9999
+	if y > 9999 {
 		return nil, nil
 	}
-	return newEvalDate(*dt, env.sqlmode.AllowZeroDate()), nil
+
+	dt := datetime.NewDateFromStd(time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, env.currentTimezone()))
+	return newEvalDate(dt, true), nil
 }
 
 func (call *builtinFromDays) compile(c *compiler) (ctype, error) {
diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go
index 47d097f345c..8da716a219f 100644
--- a/go/vt/vtgate/evalengine/testcases/cases.go
+++ b/go/vt/vtgate/evalengine/testcases/cases.go
@@ -1779,6 +1779,7 @@ func FnFromDays(yield Query) {
 		"365242",
 		"3652424",
 		"3652425",
+		"730669",
 	}
 
 	for _, d := range days {

From c4eba9d6aa39d9e8ddd031bff32cc92d3a4b00db Mon Sep 17 00:00:00 2001
From: Noble Mittal <noblemittal@outlook.com>
Date: Sun, 28 Jan 2024 04:40:44 +0530
Subject: [PATCH 4/8] Remove unnecessary check from Fn_FROM_DAYS

Signed-off-by: Noble Mittal <noblemittal@outlook.com>
---
 go/vt/vtgate/evalengine/compiler_asm.go | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go
index c14bed15f33..b0dd2adf16c 100644
--- a/go/vt/vtgate/evalengine/compiler_asm.go
+++ b/go/vt/vtgate/evalengine/compiler_asm.go
@@ -3799,11 +3799,6 @@ func (asm *assembler) Fn_LAST_DAY() {
 func (asm *assembler) Fn_FROM_DAYS() {
 	asm.emit(func(env *ExpressionEnv) int {
 		arg := env.vm.stack[env.vm.sp-1].(*evalInt64)
-		if arg == nil {
-			env.vm.stack[env.vm.sp-1] = nil
-			return 1
-		}
-
 		y, m, d := mysqldt.MysqlDateFromDayNumber(int(arg.i))
 		if y == 0 && m == 0 && d == 0 {
 			env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(datetime.Date{})

From 88d7b765a02fda789878bf0724408ef1d4441cc3 Mon Sep 17 00:00:00 2001
From: Noble Mittal <noblemittal@outlook.com>
Date: Sun, 28 Jan 2024 04:53:49 +0530
Subject: [PATCH 5/8] Add more edge cases for FROM_DAYS test

Signed-off-by: Noble Mittal <noblemittal@outlook.com>
---
 go/vt/vtgate/evalengine/testcases/cases.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go
index 8da716a219f..682d6134c7e 100644
--- a/go/vt/vtgate/evalengine/testcases/cases.go
+++ b/go/vt/vtgate/evalengine/testcases/cases.go
@@ -1779,6 +1779,8 @@ func FnFromDays(yield Query) {
 		"365242",
 		"3652424",
 		"3652425",
+		"3652500",
+		"3652499",
 		"730669",
 	}
 

From 871191f198e742901d2a00d2ea5188dccd5f3b12 Mon Sep 17 00:00:00 2001
From: Noble Mittal <noblemittal@outlook.com>
Date: Sun, 28 Jan 2024 13:34:57 +0530
Subject: [PATCH 6/8] Remove double export and return zerodate for nil arg

Signed-off-by: Noble Mittal <noblemittal@outlook.com>
---
 go/vt/vtgate/evalengine/compiler_asm.go |  3 +--
 go/vt/vtgate/evalengine/fn_time.go      | 10 ++++++----
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go
index b0dd2adf16c..8b44cd69f1c 100644
--- a/go/vt/vtgate/evalengine/compiler_asm.go
+++ b/go/vt/vtgate/evalengine/compiler_asm.go
@@ -37,7 +37,6 @@ import (
 
 	"vitess.io/vitess/go/mysql/collations/charset/types"
 	"vitess.io/vitess/go/mysql/collations/colldata"
-	mysqldt "vitess.io/vitess/go/mysql/datetime"
 
 	"vitess.io/vitess/go/hack"
 	"vitess.io/vitess/go/mysql/collations"
@@ -3799,7 +3798,7 @@ func (asm *assembler) Fn_LAST_DAY() {
 func (asm *assembler) Fn_FROM_DAYS() {
 	asm.emit(func(env *ExpressionEnv) int {
 		arg := env.vm.stack[env.vm.sp-1].(*evalInt64)
-		y, m, d := mysqldt.MysqlDateFromDayNumber(int(arg.i))
+		y, m, d := datetime.MysqlDateFromDayNumber(int(arg.i))
 		if y == 0 && m == 0 && d == 0 {
 			env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(datetime.Date{})
 		} else if y > 9999 {
diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go
index 6eb8db8c721..2c615002684 100644
--- a/go/vt/vtgate/evalengine/fn_time.go
+++ b/go/vt/vtgate/evalengine/fn_time.go
@@ -23,7 +23,6 @@ import (
 	"vitess.io/vitess/go/hack"
 	"vitess.io/vitess/go/mysql/collations"
 	"vitess.io/vitess/go/mysql/datetime"
-	mysqldt "vitess.io/vitess/go/mysql/datetime"
 	"vitess.io/vitess/go/mysql/decimal"
 	"vitess.io/vitess/go/sqltypes"
 )
@@ -1257,16 +1256,19 @@ func (call *builtinLastDay) compile(c *compiler) (ctype, error) {
 
 func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) {
 	arg, err := b.arg1(env)
-	if arg == nil || err != nil {
+	if arg == nil {
 		return nil, nil
 	}
+	if err != nil {
+		return nil, err
+	}
 
 	days := evalToInt64(arg).i
-	y, m, d := mysqldt.MysqlDateFromDayNumber(int(days))
+	y, m, d := datetime.MysqlDateFromDayNumber(int(days))
 
 	// mysql returns 0000-00-00 for days below 366 and above 3652499
 	if y == 0 && m == 0 && d == 0 {
-		return nil, nil
+		return newEvalDate(datetime.Date{}, true), nil
 	}
 
 	// mysql returns NULL if y is greater than 9999

From 9a73127b7877135def929baeab321d601af92bce Mon Sep 17 00:00:00 2001
From: Noble Mittal <noblemittal@outlook.com>
Date: Sun, 28 Jan 2024 14:31:06 +0530
Subject: [PATCH 7/8] Add DateFromDayNumber func to return Date from day number

Signed-off-by: Noble Mittal <noblemittal@outlook.com>
---
 go/mysql/datetime/datetime.go    |  4 ++--
 go/mysql/datetime/mydate.go      | 16 ++++++++++++----
 go/mysql/datetime/mydate_test.go |  2 +-
 3 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go
index 1bd5d9ce39d..67191e5c48e 100644
--- a/go/mysql/datetime/datetime.go
+++ b/go/mysql/datetime/datetime.go
@@ -615,13 +615,13 @@ func (dt *DateTime) addInterval(itv *Interval) bool {
 			return false
 		}
 
-		dt.Date.year, dt.Date.month, dt.Date.day = MysqlDateFromDayNumber(daynum)
+		dt.Date.year, dt.Date.month, dt.Date.day = mysqlDateFromDayNumber(daynum)
 		return true
 
 	case itv.unit.HasDayParts():
 		daynum := mysqlDayNumber(dt.Date.Year(), dt.Date.Month(), dt.Date.Day())
 		daynum += itv.day
-		dt.Date.year, dt.Date.month, dt.Date.day = MysqlDateFromDayNumber(daynum)
+		dt.Date.year, dt.Date.month, dt.Date.day = mysqlDateFromDayNumber(daynum)
 		return true
 
 	case itv.unit.HasMonthParts():
diff --git a/go/mysql/datetime/mydate.go b/go/mysql/datetime/mydate.go
index c172ff7de6f..5a566fc36c1 100644
--- a/go/mysql/datetime/mydate.go
+++ b/go/mysql/datetime/mydate.go
@@ -23,8 +23,8 @@ package datetime
 // day count that traditional datetime systems use (e.g. the daycount
 // algorithm in the Go standard library). It is often off by one, possibly
 // because of incorrect leap year handling, but the inverse of the algorithm
-// in MysqlDateFromDayNumber takes this into account. Hence, the results
-// of this function can ONLY be passed to MysqlDateFromDayNumber; using
+// in mysqlDateFromDayNumber takes this into account. Hence, the results
+// of this function can ONLY be passed to mysqlDateFromDayNumber; using
 // a day number with one of Go's datetime APIs will return incorrect results.
 // This API should only be used when performing datetime calculations (addition
 // and subtraction), so that the results match MySQL's. All other date handling
@@ -46,7 +46,7 @@ func mysqlDayNumber(year, month, day int) int {
 	return days + year/4 - leapAdjust
 }
 
-// MysqlDateFromDayNumber converts an absolute day number into a date (a year, month, day triplet).
+// mysqlDateFromDayNumber converts an absolute day number into a date (a year, month, day triplet).
 // This is an algorithm that has been reverse engineered from MySQL;
 // the tables used as a reference can be found in `testdata/daynr_to_date.json`.
 // See the warning from mysqlDayNumber: the day number used as an argument to
@@ -54,7 +54,7 @@ func mysqlDayNumber(year, month, day int) int {
 // This API should only be used when performing datetime calculations (addition
 // and subtraction), so that the results match MySQL's. All other date handling
 // operations must use our helpers based on Go's standard library.
-func MysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) {
+func mysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) {
 	if daynr <= 365 || daynr >= 3652500 {
 		return 0, 0, 0
 	}
@@ -81,3 +81,11 @@ func MysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) {
 
 	panic("unreachable: yday is too large?")
 }
+
+// DateFromDayNumber converts an absolute day number into a Date.
+// Returns zero date if day number exceeds 3652499 or is less than 366.
+func DateFromDayNumber(daynr int) Date {
+	var d Date
+	d.year, d.month, d.day = mysqlDateFromDayNumber(daynr)
+	return d
+}
diff --git a/go/mysql/datetime/mydate_test.go b/go/mysql/datetime/mydate_test.go
index d3662df9c59..29ecd2df9d2 100644
--- a/go/mysql/datetime/mydate_test.go
+++ b/go/mysql/datetime/mydate_test.go
@@ -49,7 +49,7 @@ func TestDayNumberFields(t *testing.T) {
 	require.NoError(t, err)
 
 	for _, tc := range expected {
-		y, m, d := MysqlDateFromDayNumber(tc[0])
+		y, m, d := mysqlDateFromDayNumber(tc[0])
 		assert.Equal(t, tc[1], int(y))
 		assert.Equal(t, tc[2], int(m))
 		assert.Equal(t, tc[3], int(d))

From e44193af322d12543036ff8f3101259f991dd690 Mon Sep 17 00:00:00 2001
From: Noble Mittal <noblemittal@outlook.com>
Date: Sun, 28 Jan 2024 14:32:53 +0530
Subject: [PATCH 8/8] Make use of DateFromDayNumber in FROM_DAYS func

Signed-off-by: Noble Mittal <noblemittal@outlook.com>
---
 go/vt/vtgate/evalengine/compiler_asm.go | 12 +++++-------
 go/vt/vtgate/evalengine/fn_time.go      | 16 ++++------------
 2 files changed, 9 insertions(+), 19 deletions(-)

diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go
index 8b44cd69f1c..91c9915186c 100644
--- a/go/vt/vtgate/evalengine/compiler_asm.go
+++ b/go/vt/vtgate/evalengine/compiler_asm.go
@@ -3798,15 +3798,13 @@ func (asm *assembler) Fn_LAST_DAY() {
 func (asm *assembler) Fn_FROM_DAYS() {
 	asm.emit(func(env *ExpressionEnv) int {
 		arg := env.vm.stack[env.vm.sp-1].(*evalInt64)
-		y, m, d := datetime.MysqlDateFromDayNumber(int(arg.i))
-		if y == 0 && m == 0 && d == 0 {
-			env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(datetime.Date{})
-		} else if y > 9999 {
+		d := datetime.DateFromDayNumber(int(arg.i))
+		if d.Year() > 9999 {
 			env.vm.stack[env.vm.sp-1] = nil
-		} else {
-			dt := datetime.NewDateFromStd(time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, env.currentTimezone()))
-			env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(dt)
+			return 1
 		}
+
+		env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(d)
 		return 1
 	}, "FN FROM_DAYS INT64(SP-1)")
 }
diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go
index 2c615002684..bf4bfb71934 100644
--- a/go/vt/vtgate/evalengine/fn_time.go
+++ b/go/vt/vtgate/evalengine/fn_time.go
@@ -1263,21 +1263,13 @@ func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) {
 		return nil, err
 	}
 
-	days := evalToInt64(arg).i
-	y, m, d := datetime.MysqlDateFromDayNumber(int(days))
+	d := datetime.DateFromDayNumber(int(evalToInt64(arg).i))
 
-	// mysql returns 0000-00-00 for days below 366 and above 3652499
-	if y == 0 && m == 0 && d == 0 {
-		return newEvalDate(datetime.Date{}, true), nil
-	}
-
-	// mysql returns NULL if y is greater than 9999
-	if y > 9999 {
+	// mysql returns NULL if year is greater than 9999
+	if d.Year() > 9999 {
 		return nil, nil
 	}
-
-	dt := datetime.NewDateFromStd(time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, env.currentTimezone()))
-	return newEvalDate(dt, true), nil
+	return newEvalDate(d, true), nil
 }
 
 func (call *builtinFromDays) compile(c *compiler) (ctype, error) {