diff --git a/go/test/endtoend/vtgate/queries/misc/misc_test.go b/go/test/endtoend/vtgate/queries/misc/misc_test.go index 8e47bb8581a..69108b26293 100644 --- a/go/test/endtoend/vtgate/queries/misc/misc_test.go +++ b/go/test/endtoend/vtgate/queries/misc/misc_test.go @@ -17,6 +17,7 @@ limitations under the License. package misc import ( + "context" "database/sql" "fmt" "strconv" @@ -24,6 +25,8 @@ import ( "testing" "time" + "vitess.io/vitess/go/mysql" + _ "github.com/go-sql-driver/mysql" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -473,3 +476,49 @@ func TestEnumSetVals(t *testing.T) { mcmp.AssertMatches("select id, enum_col, cast(enum_col as signed) from tbl_enum_set order by enum_col, id", `[[INT64(4) ENUM("xsmall") INT64(1)] [INT64(2) ENUM("small") INT64(2)] [INT64(1) ENUM("medium") INT64(3)] [INT64(5) ENUM("medium") INT64(3)] [INT64(3) ENUM("large") INT64(4)]]`) mcmp.AssertMatches("select id, set_col, cast(set_col as unsigned) from tbl_enum_set order by set_col, id", `[[INT64(4) SET("a,b") UINT64(3)] [INT64(3) SET("c") UINT64(4)] [INT64(5) SET("a,d") UINT64(9)] [INT64(1) SET("a,b,e") UINT64(19)] [INT64(2) SET("e,f,g") UINT64(112)]]`) } + +func TestTimeZones(t *testing.T) { + testCases := []struct { + name string + targetTZ string + expectedDiff time.Duration + }{ + {"UTC to +08:00", "+08:00", 8 * time.Hour}, + {"UTC to -08:00", "-08:00", -8 * time.Hour}, + {"UTC to +05:30", "+05:30", 5*time.Hour + 30*time.Minute}, + {"UTC to -05:45", "-05:45", -(5*time.Hour + 45*time.Minute)}, + {"UTC to +09:00", "+09:00", 9 * time.Hour}, + {"UTC to -12:00", "-12:00", -12 * time.Hour}, + } + + // Connect to Vitess + conn, err := mysql.Connect(context.Background(), &vtParams) + require.NoError(t, err) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Set the initial time zone and get the time + utils.Exec(t, conn, "set time_zone = '+00:00'") + rs1 := utils.Exec(t, conn, "select now()") + + // Set the target time zone and get the time + utils.Exec(t, conn, fmt.Sprintf("set time_zone = '%s'", tc.targetTZ)) + rs2 := utils.Exec(t, conn, "select now()") + + // Parse the times from the query result + layout := "2006-01-02 15:04:05" // MySQL default datetime format + time1, err := time.Parse(layout, rs1.Rows[0][0].ToString()) + require.NoError(t, err) + time2, err := time.Parse(layout, rs2.Rows[0][0].ToString()) + require.NoError(t, err) + + // Calculate the actual difference between time2 and time1 + actualDiff := time2.Sub(time1) + allowableDeviation := time.Second // allow up to 1-second difference + + // Use a range to allow for slight variations + require.InDeltaf(t, tc.expectedDiff.Seconds(), actualDiff.Seconds(), allowableDeviation.Seconds(), + "time2 should be approximately %v after time1, within 1 second tolerance\n%v vs %v", tc.expectedDiff, time1, time2) + }) + } +} diff --git a/go/vt/vtgate/executor_set_test.go b/go/vt/vtgate/executor_set_test.go index 2792c957edd..12e8e272bd7 100644 --- a/go/vt/vtgate/executor_set_test.go +++ b/go/vt/vtgate/executor_set_test.go @@ -625,3 +625,22 @@ func TestExecutorSetAndSelect(t *testing.T) { }) } } + +// TestTimeZone verifies that setting different time zones in the session +// results in different outputs for the `now()` function. +func TestExecutorTimeZone(t *testing.T) { + e, _, _, _, ctx := createExecutorEnv(t) + + session := NewAutocommitSession(&vtgatepb.Session{TargetString: KsTestUnsharded, EnableSystemSettings: true}) + session.SetSystemVariable("time_zone", "'+08:00'") + + qr, err := e.Execute(ctx, nil, "TestExecutorSetAndSelect", session, "select now()", nil) + + require.NoError(t, err) + session.SetSystemVariable("time_zone", "'+02:00'") + + qrWith, err := e.Execute(ctx, nil, "TestExecutorSetAndSelect", session, "select now()", nil) + require.NoError(t, err) + + assert.False(t, qr.Rows[0][0].Equal(qrWith.Rows[0][0]), "%v vs %v", qr.Rows[0][0].ToString(), qrWith.Rows[0][0].ToString()) +} diff --git a/go/vt/vtgate/safe_session.go b/go/vt/vtgate/safe_session.go index 45fff46f629..1d57c63ef35 100644 --- a/go/vt/vtgate/safe_session.go +++ b/go/vt/vtgate/safe_session.go @@ -23,6 +23,8 @@ import ( "sync" "time" + "vitess.io/vitess/go/sqltypes" + "google.golang.org/protobuf/proto" "vitess.io/vitess/go/mysql/datetime" @@ -563,13 +565,21 @@ func (session *SafeSession) HasSystemVariables() (found bool) { func (session *SafeSession) TimeZone() *time.Location { session.mu.Lock() - tz, ok := session.SystemVariables["time_zone"] + zoneSQL, ok := session.SystemVariables["time_zone"] session.mu.Unlock() if !ok { return nil } + + tz, err := sqltypes.DecodeStringSQL(zoneSQL) + if err != nil { + return nil + } + loc, _ := datetime.ParseTimeZone(tz) + // it's safe to ignore the error - if we get an error, loc will be nil, + // and this is exactly the behaviour we want anyway return loc } diff --git a/go/vt/vtgate/safe_session_test.go b/go/vt/vtgate/safe_session_test.go index 21bb2d6697a..ce681fe7fd3 100644 --- a/go/vt/vtgate/safe_session_test.go +++ b/go/vt/vtgate/safe_session_test.go @@ -73,11 +73,11 @@ func TestTimeZone(t *testing.T) { want string }{ { - tz: "Europe/Amsterdam", + tz: "'Europe/Amsterdam'", want: "Europe/Amsterdam", }, { - tz: "+02:00", + tz: "'+02:00'", want: "UTC+02:00", }, {