Skip to content

Commit

Permalink
expression,types: Adjusts UNIX_TIMESTAMP() for non-existing DST values (
Browse files Browse the repository at this point in the history
  • Loading branch information
mjonss authored and birdstorm committed Sep 21, 2022
1 parent 3aa7f7e commit 6cf6be9
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 2 deletions.
2 changes: 1 addition & 1 deletion expression/builtin_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -4853,7 +4853,7 @@ func (b *builtinUnixTimestampIntSig) evalIntWithCtx(ctx sessionctx.Context, row
}

tz := ctx.GetSessionVars().Location()
t, err := val.GoTime(tz)
t, err := val.AdjustedGoTime(tz)
if err != nil {
return 0, false, nil
}
Expand Down
2 changes: 1 addition & 1 deletion expression/builtin_time_vec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2325,7 +2325,7 @@ func (b *builtinUnixTimestampIntSig) vecEvalInt(input *chunk.Chunk, result *chun
continue
}

t, err := buf.GetTime(i).GoTime(getTimeZone(b.ctx))
t, err := buf.GetTime(i).AdjustedGoTime(getTimeZone(b.ctx))
if err != nil {
i64s[i] = 0
continue
Expand Down
19 changes: 19 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9476,3 +9476,22 @@ func (s *testIntegrationSuite) TestIssue29513(c *C) {
tk.MustQuery("select '123' union select cast(a as char) from t;").Sort().Check(testkit.Rows("123", "45678"))
tk.MustQuery("select '123' union select cast(a as char(2)) from t;").Sort().Check(testkit.Rows("123", "45"))
}

func (s *testIntegrationSuite) TestIssue28739(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec(`USE test`)
tk.MustExec("SET time_zone = 'Europe/Vilnius'")
tk.MustQuery("SELECT UNIX_TIMESTAMP('2020-03-29 03:45:00')").Check(testkit.Rows("1585443600"))
tk.MustQuery("SELECT FROM_UNIXTIME(UNIX_TIMESTAMP('2020-03-29 03:45:00'))").Check(testkit.Rows("2020-03-29 04:00:00"))
tk.MustExec(`DROP TABLE IF EXISTS t`)
tk.MustExec(`CREATE TABLE t (dt DATETIME NULL)`)
defer tk.MustExec(`DROP TABLE t`)
// Test the vector implememtation
tk.MustExec(`INSERT INTO t VALUES ('2021-10-31 02:30:00'), ('2021-03-28 02:30:00'), ('2020-10-04 02:15:00'), ('2020-03-29 03:45:00'), (NULL)`)
tk.MustQuery(`SELECT dt, UNIX_TIMESTAMP(dt) FROM t`).Sort().Check(testkit.Rows(
"2020-03-29 03:45:00 1585443600",
"2020-10-04 02:15:00 1601766900",
"2021-03-28 02:30:00 1616891400",
"2021-10-31 02:30:00 1635636600",
"<nil> <nil>"))
}
53 changes: 53 additions & 0 deletions types/core_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,59 @@ func (t CoreTime) GoTime(loc *gotime.Location) (gotime.Time, error) {
return tm, nil
}

// FindZoneTransition check for one Time Zone transition within +/- 4h
// Currently the needed functions are not exported, if gotime.Location.lookup would be exported
// then it would be easy to use that directly
func FindZoneTransition(tIn gotime.Time) (gotime.Time, error) {
// Check most common case first, DST transition on full hour.
// round truncates away from zero!
t2 := tIn.Round(gotime.Hour).Add(-1 * gotime.Hour)
t1 := t2.Add(-1 * gotime.Second)
_, offset1 := t1.Zone()
_, offset2 := t2.Zone()
if offset1 != offset2 {
return t2, nil
}

// Check if any offset change?
t1 = tIn.Add(-4 * gotime.Hour)
t2 = tIn.Add(4 * gotime.Hour)
_, offset1 = t1.Zone()
_, offset2 = t2.Zone()
if offset1 == offset2 {
return tIn, errors.Trace(ErrWrongValue.GenWithStackByArgs(TimeStr, tIn))
}

// Check generic case, like for 'Australia/Lord_Howe'
for t2.After(t1.Add(gotime.Second)) {
t := t1.Add(t2.Sub(t1) / 2).Round(gotime.Second)
_, offset := t.Zone()
if offset == offset1 {
t1 = t
} else {
t2 = t
}
}
return t2, nil
}

// AdjustedGoTime converts Time to GoTime and adjust for invalid DST times
// like during the DST change with increased offset,
// normally moving to Daylight Saving Time.
// see https://github.com/pingcap/tidb/issues/28739
func (t CoreTime) AdjustedGoTime(loc *gotime.Location) (gotime.Time, error) {
tm, err := t.GoTime(loc)
if err == nil {
return tm, nil
}

tAdj, err2 := FindZoneTransition(tm)
if err2 == nil {
return tAdj, nil
}
return tm, err
}

// IsLeapYear returns if it's leap year.
func (t CoreTime) IsLeapYear() bool {
return isLeapYear(t.getYear())
Expand Down
77 changes: 77 additions & 0 deletions types/core_time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"time"

. "github.com/pingcap/check"
"github.com/stretchr/testify/require"
)

type testCoreTimeSuite struct{}
Expand Down Expand Up @@ -294,3 +295,79 @@ func (s *testCoreTimeSuite) TestWeekday(c *C) {
c.Check(weekday.String(), Equals, tt.Expect)
}
}

func (s *testCoreTimeSuite) TestFindZoneTransition(c *C) {
tests := []struct {
TZ string
dt string
Expect string
Success bool
}{
{"Australia/Lord_Howe", "2020-06-29 03:45:00", "", false},
{"Australia/Lord_Howe", "2020-10-04 02:15:00", "2020-10-04 02:30:00 +11 +1100", true},
{"Australia/Lord_Howe", "2020-10-04 02:29:59", "2020-10-04 02:30:00 +11 +1100", true},
{"Australia/Lord_Howe", "2020-10-04 02:29:59.99", "2020-10-04 02:30:00 +11 +1100", true},
{"Australia/Lord_Howe", "2020-10-04 02:30:00.0001", "2020-10-04 02:30:00 +11 +1100", true},
{"Australia/Lord_Howe", "2020-10-04 02:30:00", "2020-10-04 02:30:00 +11 +1100", true},
{"Australia/Lord_Howe", "2020-10-04 02:30:01", "2020-10-04 02:30:00 +11 +1100", true},
{"Europe/Vilnius", "2020-03-29 03:45:00", "2020-03-29 04:00:00 EEST +0300", true},
{"Europe/Vilnius", "2020-10-25 03:45:00", "2020-10-25 03:00:00 EET +0200", true},
{"Europe/Vilnius", "2020-06-29 03:45:00", "", false},
{"Europe/Amsterdam", "2020-03-29 02:45:00", "2020-03-29 03:00:00 CEST +0200", true},
{"Europe/Amsterdam", "2020-10-25 02:35:00", "2020-10-25 02:00:00 CET +0100", true},
{"Europe/Amsterdam", "2020-03-29 02:59:59", "2020-03-29 03:00:00 CEST +0200", true},
{"Europe/Amsterdam", "2020-03-29 02:59:59.999999999", "2020-03-29 03:00:00 CEST +0200", true},
{"Europe/Amsterdam", "2020-03-29 03:00:00.000000001", "2020-03-29 03:00:00 CEST +0200", true},
}

for _, tt := range tests {
loc, err := time.LoadLocation(tt.TZ)
require.NoError(c, err)
tm, err := time.ParseInLocation("2006-01-02 15:04:05", tt.dt, loc)
require.NoError(c, err)
tp, err := FindZoneTransition(tm)
if !tt.Success {
require.Error(c, err)
} else {
require.NoError(c, err)
require.Equal(c, tt.Expect, tp.Format("2006-01-02 15:04:05.999999999 MST -0700"))
}
}
}

func (s *testCoreTimeSuite) TestAdjustedGoTime(c *C) {
tests := []struct {
TZ string
dt CoreTime
Expect string
Success bool
}{
{"Australia/Lord_Howe", FromDate(2020, 10, 04, 01, 59, 59, 997), "2020-10-04 01:59:59.000997 +1030 +1030", true},
{"Australia/Lord_Howe", FromDate(2020, 10, 04, 02, 00, 00, 0), "2020-10-04 02:30:00 +11 +1100", true},
{"Australia/Lord_Howe", FromDate(2020, 10, 04, 02, 15, 00, 0), "2020-10-04 02:30:00 +11 +1100", true},
{"Australia/Lord_Howe", FromDate(2020, 10, 04, 02, 29, 59, 999999), "2020-10-04 02:30:00 +11 +1100", true},
{"Australia/Lord_Howe", FromDate(2020, 10, 04, 02, 30, 00, 1), "2020-10-04 02:30:00.000001 +11 +1100", true},
{"Australia/Lord_Howe", FromDate(2020, 06, 29, 03, 45, 00, 0), "2020-06-29 03:45:00 +1030 +1030", true},
{"Australia/Lord_Howe", FromDate(2020, 04, 04, 01, 45, 00, 0), "2020-04-04 01:45:00 +11 +1100", true},
{"Europe/Vilnius", FromDate(2020, 03, 29, 03, 45, 00, 0), "2020-03-29 04:00:00 EEST +0300", true},
{"Europe/Vilnius", FromDate(2020, 03, 29, 03, 59, 59, 456789), "2020-03-29 04:00:00 EEST +0300", true},
{"Europe/Vilnius", FromDate(2020, 03, 29, 04, 00, 01, 130000), "2020-03-29 04:00:01.13 EEST +0300", true},
{"Europe/Vilnius", FromDate(2020, 10, 25, 03, 45, 00, 0), "2020-10-25 03:45:00 EET +0200", true},
{"Europe/Vilnius", FromDate(2020, 06, 29, 03, 45, 00, 0), "2020-06-29 03:45:00 EEST +0300", true},
{"Europe/Amsterdam", FromDate(2020, 03, 29, 02, 45, 00, 0), "2020-03-29 03:00:00 CEST +0200", true},
{"Europe/Amsterdam", FromDate(2020, 10, 25, 02, 35, 00, 0), "2020-10-25 02:35:00 CET +0100", true},
{"UTC", FromDate(2020, 2, 31, 02, 35, 00, 0), "", false},
}

for _, tt := range tests {
loc, err := time.LoadLocation(tt.TZ)
require.NoError(c, err)
tp, err := tt.dt.AdjustedGoTime(loc)
if !tt.Success {
require.Error(c, err)
} else {
require.NoError(c, err)
require.Equal(c, tt.Expect, tp.Format("2006-01-02 15:04:05.999999999 MST -0700"))
}
}
}

0 comments on commit 6cf6be9

Please sign in to comment.