Skip to content

Commit 11ef8b7

Browse files
authored
Merge pull request #3113 from dolthub/angela/utc
Fix double counted time delta when converting time zones
2 parents 328e24e + 8066b53 commit 11ef8b7

File tree

15 files changed

+122
-42
lines changed

15 files changed

+122
-42
lines changed

enginetest/enginetests.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6147,6 +6147,26 @@ func TestSQLLogicTests(t *testing.T, harness Harness) {
61476147
}
61486148
}
61496149

6150+
func TestTimeQueries(t *testing.T, harness Harness) {
6151+
// "America/Phoenix" is a non-UTC time zone that does not observe daylight savings time
6152+
phoenixTimeZone, _ := time.LoadLocation("America/Phoenix")
6153+
mockNow := time.Date(2025, time.July, 23, 9, 43, 21, 0, phoenixTimeZone)
6154+
for _, script := range queries.TimeQueryTests {
6155+
if sh, ok := harness.(SkippingHarness); ok {
6156+
if sh.SkipQueryTest(script.Name) {
6157+
t.Run(script.Name, func(t *testing.T) {
6158+
t.Skip(script.Name)
6159+
})
6160+
continue
6161+
}
6162+
}
6163+
sql.RunWithNowFunc(func() time.Time { return mockNow }, func() error {
6164+
TestScript(t, harness, script)
6165+
return nil
6166+
})
6167+
}
6168+
}
6169+
61506170
// ExecuteNode builds an iterator and then drains it.
61516171
// This is useful for populating actual row counts for `DESCRIBE ANALYZE`.
61526172
func ExecuteNode(ctx *sql.Context, engine QueryEngine, node sql.Node) error {

enginetest/memory_engine_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,3 +1097,7 @@ func TestSQLLogicTestFiles(t *testing.T) {
10971097
}
10981098
logictest.RunTestFiles(h, paths...)
10991099
}
1100+
1101+
func TestTimeQueries(t *testing.T) {
1102+
enginetest.TestTimeQueries(t, enginetest.NewDefaultMemoryHarness())
1103+
}

enginetest/queries/script_queries.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"github.com/dolthub/vitess/go/vt/sqlparser"
2323
"gopkg.in/src-d/go-errors.v1"
2424

25-
gmstime "github.com/dolthub/go-mysql-server/internal/time"
2625
"github.com/dolthub/go-mysql-server/sql"
2726
"github.com/dolthub/go-mysql-server/sql/analyzer/analyzererrors"
2827
"github.com/dolthub/go-mysql-server/sql/plan"
@@ -4685,7 +4684,7 @@ CREATE TABLE tab3 (
46854684
// To match MySQL's behavior, this comes from the operating system's timezone setting
46864685
// TODO: the "global" shouldn't be necessary here, but GMS goes to session without it
46874686
Query: `select @@global.system_time_zone;`,
4688-
Expected: []sql.Row{{gmstime.SystemTimezoneOffset()}},
4687+
Expected: []sql.Row{{sql.SystemTimezoneOffset()}},
46894688
},
46904689
{
46914690
// The default time_zone setting for MySQL is SYSTEM, which means timezone comes from @@system_time_zone

enginetest/queries/time_queries.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2020-2025 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package queries
16+
17+
import (
18+
"time"
19+
20+
"github.com/dolthub/go-mysql-server/sql"
21+
"github.com/dolthub/go-mysql-server/sql/types"
22+
)
23+
24+
var TimeQueryTests = []ScriptTest{
25+
{
26+
// time zone tests the current time set as July 23, 2025 at 9:43:21am America/Phoenix (-7:00) (does not observe
27+
// daylight savings time so time zone does not change)
28+
Name: "time zone tests",
29+
SetUpScript: []string{},
30+
Assertions: []ScriptTestAssertion{
31+
{
32+
Query: "set time_zone='UTC'",
33+
Expected: []sql.Row{{types.NewOkResult(0)}},
34+
},
35+
{
36+
Query: "select now()",
37+
Expected: []sql.Row{{time.Date(2025, time.July, 23, 16, 43, 21, 0, time.UTC)}},
38+
},
39+
{
40+
Query: "set time_zone='-5:00'",
41+
Expected: []sql.Row{{types.NewOkResult(0)}},
42+
},
43+
{
44+
Query: "select now()",
45+
Expected: []sql.Row{{time.Date(2025, time.July, 23, 11, 43, 21, 0, time.UTC)}},
46+
},
47+
{
48+
// doesn't observe daylight savings time
49+
Query: "set time_zone='Pacific/Honolulu'",
50+
Expected: []sql.Row{{types.NewOkResult(0)}},
51+
},
52+
{
53+
Query: "select now()",
54+
Expected: []sql.Row{{time.Date(2025, time.July, 23, 6, 43, 21, 0, time.UTC)}},
55+
},
56+
{
57+
// https://github.com/dolthub/dolt/issues/9559
58+
Skip: true,
59+
Query: "set time_zone='invalid time zone",
60+
// update to actual error or error string
61+
ExpectedErrStr: "Unknown of incorrect time zone: 'invalid time zone'",
62+
},
63+
},
64+
},
65+
}

sql/events.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ import (
2222
"time"
2323

2424
"gopkg.in/src-d/go-errors.v1"
25-
26-
gmstime "github.com/dolthub/go-mysql-server/internal/time"
2725
)
2826

2927
const EventDateSpaceTimeFormat = "2006-01-02 15:04:05"
@@ -83,32 +81,32 @@ type EventDefinition struct {
8381
func (e *EventDefinition) ConvertTimesFromUTCToTz(tz string) *EventDefinition {
8482
ne := *e
8583
if ne.HasExecuteAt {
86-
t, ok := gmstime.ConvertTimeZone(e.ExecuteAt, "+00:00", tz)
84+
t, ok := ConvertTimeZone(e.ExecuteAt, "+00:00", tz)
8785
if ok {
8886
ne.ExecuteAt = t
8987
}
9088
} else {
91-
t, ok := gmstime.ConvertTimeZone(e.Starts, "+00:00", tz)
89+
t, ok := ConvertTimeZone(e.Starts, "+00:00", tz)
9290
if ok {
9391
ne.Starts = t
9492
}
9593
if ne.HasEnds {
96-
t, ok = gmstime.ConvertTimeZone(e.Ends, "+00:00", tz)
94+
t, ok = ConvertTimeZone(e.Ends, "+00:00", tz)
9795
if ok {
9896
ne.Ends = t
9997
}
10098
}
10199
}
102100

103-
t, ok := gmstime.ConvertTimeZone(e.CreatedAt, "+00:00", tz)
101+
t, ok := ConvertTimeZone(e.CreatedAt, "+00:00", tz)
104102
if ok {
105103
ne.CreatedAt = t
106104
}
107-
t, ok = gmstime.ConvertTimeZone(e.LastAltered, "+00:00", tz)
105+
t, ok = ConvertTimeZone(e.LastAltered, "+00:00", tz)
108106
if ok {
109107
ne.LastAltered = t
110108
}
111-
t, ok = gmstime.ConvertTimeZone(e.LastExecuted, "+00:00", tz)
109+
t, ok = ConvertTimeZone(e.LastExecuted, "+00:00", tz)
112110
if ok {
113111
ne.LastExecuted = t
114112
}
@@ -363,7 +361,7 @@ var tzRegex = regexp.MustCompile(`(?m)^([+\-])(\d{2}):(\d{2})$`)
363361
// evaluating valid MySQL datetime and timestamp formats.
364362
func GetTimeValueFromStringInput(field, t string) (time.Time, error) {
365363
// TODO: the time value should be in session timezone rather than system timezone.
366-
sessTz := gmstime.SystemTimezoneOffset()
364+
sessTz := SystemTimezoneOffset()
367365

368366
// For MySQL datetime format, it accepts any valid date format
369367
// and tries parsing time part first and timezone part if time part is valid.
@@ -407,7 +405,7 @@ func GetTimeValueFromStringInput(field, t string) (time.Time, error) {
407405
}
408406

409407
// convert the time value to the session timezone for display and storage
410-
tVal, ok = gmstime.ConvertTimeZone(tVal, inputTz, sessTz)
408+
tVal, ok = ConvertTimeZone(tVal, inputTz, sessTz)
411409
if !ok {
412410
return time.Time{}, fmt.Errorf("invalid time zone: %s", sessTz)
413411
}

sql/expression/function/convert_tz.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package function
1717
import (
1818
"fmt"
1919

20-
gmstime "github.com/dolthub/go-mysql-server/internal/time"
2120
"github.com/dolthub/go-mysql-server/sql"
2221
"github.com/dolthub/go-mysql-server/sql/types"
2322
)
@@ -104,7 +103,7 @@ func (c *ConvertTz) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
104103
}
105104

106105
if fromStr == "SYSTEM" {
107-
fromStr = gmstime.SystemTimezoneOffset()
106+
fromStr = sql.SystemTimezoneOffset()
108107
}
109108

110109
toStr, ok := to.(string)
@@ -113,10 +112,10 @@ func (c *ConvertTz) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
113112
}
114113

115114
if toStr == "SYSTEM" {
116-
toStr = gmstime.SystemTimezoneOffset()
115+
toStr = sql.SystemTimezoneOffset()
117116
}
118117

119-
converted, success := gmstime.ConvertTimeZone(datetime, fromStr, toStr)
118+
converted, success := sql.ConvertTimeZone(datetime, fromStr, toStr)
120119
if !success {
121120
return nil, nil
122121
}

sql/expression/function/date.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222

2323
"github.com/shopspring/decimal"
2424

25-
gmstime "github.com/dolthub/go-mysql-server/internal/time"
2625
"github.com/dolthub/go-mysql-server/sql"
2726
"github.com/dolthub/go-mysql-server/sql/expression"
2827
"github.com/dolthub/go-mysql-server/sql/transform"
@@ -314,7 +313,7 @@ func (ut *UnixTimestamp) Eval(ctx *sql.Context, row sql.Row) (interface{}, error
314313
return nil, err
315314
}
316315

317-
ctz, ok := gmstime.ConvertTimeZone(date.(time.Time), stz, "UTC")
316+
ctz, ok := sql.ConvertTimeZone(date.(time.Time), stz, "UTC")
318317
if ok {
319318
date = ctz
320319
}
@@ -408,7 +407,7 @@ func (r *FromUnixtime) Eval(ctx *sql.Context, row sql.Row) (interface{}, error)
408407
if err != nil {
409408
return nil, err
410409
}
411-
t, _ = gmstime.ConvertTimeZone(t, "UTC", tz)
410+
t, _ = sql.ConvertTimeZone(t, "UTC", tz)
412411
if len(vals) == 1 {
413412
return t, nil // If format is omitted, this function returns a DATETIME value.
414413
}

sql/expression/function/time.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222

2323
"gopkg.in/src-d/go-errors.v1"
2424

25-
gmstime "github.com/dolthub/go-mysql-server/internal/time"
2625
"github.com/dolthub/go-mysql-server/sql"
2726
"github.com/dolthub/go-mysql-server/sql/expression"
2827
"github.com/dolthub/go-mysql-server/sql/types"
@@ -1016,7 +1015,7 @@ func (n *Now) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
10161015
// If no arguments, just return with 0 precision
10171016
// The way the parser is implemented 0 should always be passed in; have this here just in case
10181017
if n.prec == nil {
1019-
t, ok := gmstime.ConvertTimeZone(currentTime, gmstime.SystemTimezoneOffset(), sessionTimeZone)
1018+
t, ok := sql.ConvertTimeZone(currentTime, sql.SystemTimezoneOffset(), sessionTimeZone)
10201019
if !ok {
10211020
return nil, fmt.Errorf("invalid time zone: %s", sessionTimeZone)
10221021
}
@@ -1056,7 +1055,7 @@ func (n *Now) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
10561055
}
10571056

10581057
// Get the timestamp
1059-
t, ok := gmstime.ConvertTimeZone(currentTime, gmstime.SystemTimezoneOffset(), sessionTimeZone)
1058+
t, ok := sql.ConvertTimeZone(currentTime, sql.SystemTimezoneOffset(), sessionTimeZone)
10601059
if !ok {
10611060
return nil, fmt.Errorf("invalid time zone: %s", sessionTimeZone)
10621061
}
@@ -1106,7 +1105,7 @@ func SessionTimeZone(ctx *sql.Context) (string, error) {
11061105
}
11071106

11081107
if sessionTimeZone == "SYSTEM" {
1109-
sessionTimeZone = gmstime.SystemTimezoneOffset()
1108+
sessionTimeZone = sql.SystemTimezoneOffset()
11101109
}
11111110
return sessionTimeZone, nil
11121111
}

sql/information_schema/information_schema.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"github.com/dolthub/vitess/go/sqltypes"
2525
"github.com/dolthub/vitess/go/vt/sqlparser"
2626

27-
gmstime "github.com/dolthub/go-mysql-server/internal/time"
2827
. "github.com/dolthub/go-mysql-server/sql"
2928
"github.com/dolthub/go-mysql-server/sql/mysql_db"
3029
"github.com/dolthub/go-mysql-server/sql/plan"
@@ -965,7 +964,7 @@ func eventsRowIter(ctx *Context, c Catalog) (RowIter, error) {
965964
}
966965

967966
for _, e := range eventDefs {
968-
ed := e.ConvertTimesFromUTCToTz(gmstime.SystemTimezoneOffset())
967+
ed := e.ConvertTimesFromUTCToTz(SystemTimezoneOffset())
969968
var at, intervalVal, intervalField, starts, ends interface{}
970969
var eventType, status string
971970
if ed.HasExecuteAt {

sql/plan/alter_event.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222

2323
"github.com/dolthub/vitess/go/mysql"
2424

25-
gmstime "github.com/dolthub/go-mysql-server/internal/time"
2625
"github.com/dolthub/go-mysql-server/sql"
2726
"github.com/dolthub/go-mysql-server/sql/expression"
2827
"github.com/dolthub/go-mysql-server/sql/types"
@@ -241,7 +240,7 @@ func (a *AlterEvent) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error)
241240
var err error
242241
ed := a.Event
243242
eventAlteredTime := ctx.QueryTime()
244-
sysTz := gmstime.SystemTimezoneOffset()
243+
sysTz := sql.SystemTimezoneOffset()
245244
ed.LastAltered = eventAlteredTime
246245
ed.Definer = a.Definer
247246

0 commit comments

Comments
 (0)