Repository: pingcap/tidb (Go)

Issue:
statement table `FIRST_SEEN`, `LAST_SEEN` field value is not sync with `time_zone` variable
## Bug Report

Please answer these questions before submitting your issue. Thanks!

### 1. Minimal reproduce step (Required)

<!-- a step by step guide for reproducing the bug. -->

Change `time_zone` to another value that is different from `system_time_zone`.

```
mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.12 sec)

mysql> set global time_zone='+09:00';

// exit and reconnect

mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | +09:00 |
+------------------+--------+
2 rows in set (0.12 sec)
```

Check current time:

```
mysql> select now();
+---------------------+
| now()               |
+---------------------+
| 2022-02-08 15:16:07 |
+---------------------+
1 row in set (0.04 sec)
```

Run some SQLs:

```
mysql> select sleep(1);
+----------+
| sleep(1) |
+----------+
|        0 |
+----------+
1 row in set (1.10 sec)

mysql> select sleep(1);
+----------+
| sleep(1) |
+----------+
|        0 |
+----------+
1 row in set (1.04 sec)
```

Query statement table:

```
mysql> select FIRST_SEEN, LAST_SEEN, QUERY_SAMPLE_TEXT from INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY order by LAST_SEEN desc limit 1\G
*************************** 1. row ***************************
       FIRST_SEEN: 2022-02-08 06:16:21
        LAST_SEEN: 2022-02-08 06:16:33
QUERY_SAMPLE_TEXT: select `sleep` ( ? )
1 row in set (0.14 sec)
```


### 2. What did you expect to see? (Required)

The `FIRST_SEEN` value should be the time of +9:00 time zone instead of UTC time zone (aka `system_time_zone` in this case). same as `LAST_SEEN`, `SUMMARY_BEGIN_TIME` and `SUMMARY_END_TIME` field.

### 3. What did you see instead (Required)

These filed values are shown as the UTC time zone (aka `system_time_zone` in this case).

### 4. What is your TiDB version? (Required)

<!-- Paste the output of SELECT tidb_version() -->

```
mysql> select version();
+--------------------+
| version()          |
+--------------------+
| 5.7.25-TiDB-v5.3.0 |
+--------------------+
1 row in set (0.10 sec)
```


Discussion:
/cc @djshow832 @crazycs520 
@LemonHX PTAL
Related to #26642 but for different table/commands.
For FIRST_SEEN and LAST_SEEN, the implementation sends the times in go time datatype in the location of the tidb server global system_time_zone, we need to propagate the session local `time_zone` location down to the FirstSeenStr/LastSeenStr entries in the columnValueFactoryMap, and add `.In(<session local location>)` like:
`return types.NewTime(types.FromGoTime(ssElement.firstSeen.In(<session local time zone location>)), mysql.TypeTimestamp, 0)`



Patch:
diff --git a/util/stmtsummary/reader.go b/util/stmtsummary/reader.go
index 6551a275239b7..a0003d9390eef 100644
--- a/util/stmtsummary/reader.go
+++ b/util/stmtsummary/reader.go
@@ -313,11 +313,19 @@ var columnValueFactoryMap = map[string]columnValueFactory{
 	ClusterTableInstanceColumnNameStr: func(reader *stmtSummaryReader, ssElement *stmtSummaryByDigestElement, ssbd *stmtSummaryByDigest) interface{} {
 		return reader.instanceAddr
 	},
-	SummaryBeginTimeStr: func(_ *stmtSummaryReader, ssElement *stmtSummaryByDigestElement, _ *stmtSummaryByDigest) interface{} {
-		return types.NewTime(types.FromGoTime(time.Unix(ssElement.beginTime, 0)), mysql.TypeTimestamp, 0)
+	SummaryBeginTimeStr: func(reader *stmtSummaryReader, ssElement *stmtSummaryByDigestElement, _ *stmtSummaryByDigest) interface{} {
+		beginTime := time.Unix(ssElement.beginTime, 0)
+		if beginTime.Location() != reader.tz {
+			beginTime = beginTime.In(reader.tz)
+		}
+		return types.NewTime(types.FromGoTime(beginTime), mysql.TypeTimestamp, 0)
 	},
-	SummaryEndTimeStr: func(_ *stmtSummaryReader, ssElement *stmtSummaryByDigestElement, _ *stmtSummaryByDigest) interface{} {
-		return types.NewTime(types.FromGoTime(time.Unix(ssElement.endTime, 0)), mysql.TypeTimestamp, 0)
+	SummaryEndTimeStr: func(reader *stmtSummaryReader, ssElement *stmtSummaryByDigestElement, _ *stmtSummaryByDigest) interface{} {
+		endTime := time.Unix(ssElement.endTime, 0)
+		if endTime.Location() != reader.tz {
+			endTime = endTime.In(reader.tz)
+		}
+		return types.NewTime(types.FromGoTime(endTime), mysql.TypeTimestamp, 0)
 	},
 	StmtTypeStr: func(_ *stmtSummaryReader, _ *stmtSummaryByDigestElement, ssbd *stmtSummaryByDigest) interface{} {
 		return ssbd.stmtType


Test patch:
diff --git a/infoschema/cluster_tables_test.go b/infoschema/cluster_tables_test.go
index 8f2ab2c81990e..258f35141c0f7 100644
--- a/infoschema/cluster_tables_test.go
+++ b/infoschema/cluster_tables_test.go
@@ -415,20 +415,30 @@ func TestStmtSummaryHistoryTableWithUserTimezone(t *testing.T) {
 	tk.MustExec("use test;")
 	tk.MustExec("set time_zone = '+08:00';")
 	tk.MustExec("select sleep(0.1);")
-	r := tk.MustQuery("select FIRST_SEEN, LAST_SEEN from INFORMATION_SCHEMA.STATEMENTS_SUMMARY_HISTORY order by LAST_SEEN limit 1;")
+	r := tk.MustQuery("select FIRST_SEEN, LAST_SEEN, SUMMARY_BEGIN_TIME, SUMMARY_END_TIME from INFORMATION_SCHEMA.STATEMENTS_SUMMARY_HISTORY order by LAST_SEEN limit 1;")
 	date8First, err := time.Parse("2006-01-02 15:04:05", r.Rows()[0][0].(string))
 	require.NoError(t, err)
 	date8Last, err := time.Parse("2006-01-02 15:04:05", r.Rows()[0][1].(string))
 	require.NoError(t, err)
+	date8Begin, err := time.Parse("2006-01-02 15:04:05", r.Rows()[0][2].(string))
+	require.NoError(t, err)
+	date8End, err := time.Parse("2006-01-02 15:04:05", r.Rows()[0][3].(string))
+	require.NoError(t, err)
 	tk.MustExec("set time_zone = '+01:00';")
-	r = tk.MustQuery("select FIRST_SEEN, LAST_SEEN from INFORMATION_SCHEMA.STATEMENTS_SUMMARY_HISTORY order by LAST_SEEN limit 1;")
+	r = tk.MustQuery("select FIRST_SEEN, LAST_SEEN, SUMMARY_BEGIN_TIME, SUMMARY_END_TIME from INFORMATION_SCHEMA.STATEMENTS_SUMMARY_HISTORY order by LAST_SEEN limit 1;")
 	date1First, err := time.Parse("2006-01-02 15:04:05", r.Rows()[0][0].(string))
 	require.NoError(t, err)
 	date1Last, err := time.Parse("2006-01-02 15:04:05", r.Rows()[0][1].(string))
 	require.NoError(t, err)
+	date1Begin, err := time.Parse("2006-01-02 15:04:05", r.Rows()[0][2].(string))
+	require.NoError(t, err)
+	date1End, err := time.Parse("2006-01-02 15:04:05", r.Rows()[0][3].(string))
+	require.NoError(t, err)
 
 	require.Less(t, date1First.Unix(), date8First.Unix())
 	require.Less(t, date1Last.Unix(), date8Last.Unix())
+	require.Less(t, date1Begin.Unix(), date8Begin.Unix())
+	require.Less(t, date1End.Unix(), date8End.Unix())
 }
 
 func TestStmtSummaryHistoryTable(t *testing.T) {
diff --git a/util/stmtsummary/statement_summary_test.go b/util/stmtsummary/statement_summary_test.go
index c883b08005c92..a84eb8799af07 100644
--- a/util/stmtsummary/statement_summary_test.go
+++ b/util/stmtsummary/statement_summary_test.go
@@ -758,8 +758,8 @@ func TestToDatum(t *testing.T) {
 	reader := newStmtSummaryReaderForTest(ssMap)
 	datums := reader.GetStmtSummaryCurrentRows()
 	require.Equal(t, 1, len(datums))
-	n := types.NewTime(types.FromGoTime(time.Unix(ssMap.beginTimeForCurInterval, 0)), mysql.TypeTimestamp, types.DefaultFsp)
-	e := types.NewTime(types.FromGoTime(time.Unix(ssMap.beginTimeForCurInterval+1800, 0)), mysql.TypeTimestamp, types.DefaultFsp)
+	n := types.NewTime(types.FromGoTime(time.Unix(ssMap.beginTimeForCurInterval, 0).In(time.UTC)), mysql.TypeTimestamp, types.DefaultFsp)
+	e := types.NewTime(types.FromGoTime(time.Unix(ssMap.beginTimeForCurInterval+1800, 0).In(time.UTC)), mysql.TypeTimestamp, types.DefaultFsp)
 	f := types.NewTime(types.FromGoTime(stmtExecInfo1.StartTime), mysql.TypeTimestamp, types.DefaultFsp)
 	stmtExecInfo1.ExecDetail.CommitDetail.Mu.Lock()
 	expectedDatum := []interface{}{n, e, "Select", stmtExecInfo1.SchemaName, stmtExecInfo1.Digest, stmtExecInfo1.NormalizedSQL,