Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/executor/test/simpletest/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ go_test(
],
flaky = True,
race = "on",
shard_count = 12,
shard_count = 13,
deps = [
"//pkg/config",
"//pkg/parser/ast",
Expand Down
41 changes: 41 additions & 0 deletions pkg/executor/test/simpletest/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -818,3 +818,44 @@ func TestKillStmt(t *testing.T) {
tk.MustExecToErr("kill rand()", "Invalid operation. Please use 'KILL TIDB [CONNECTION | QUERY] [connectionID | CONNECTION_ID()]' instead")
// remote kill is tested in `tests/globalkilltest`
}

func TestSelectWhereInvalidDSTTime(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("create table t (id int, ts timestamp)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume timestamp and timestamp(6) are expected to behave the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I just tested manually and it is the same, but would prefer to not include timestamp(6) in this test. I am currently working on a more covering tests for timestamp, which also includes timestamp with fractions, which should cover this as well.

tk.MustExec(`set time_zone = "UTC"`)
tk.MustExec("insert into t values (1, '1970-01-01 00:00:01')")
tk.MustExec("insert into t values (2, '2025-03-30 00:59:59')")
tk.MustExec("insert into t values (3, '2025-03-30 01:00:00')")
tk.MustExec(`set time_zone = "Europe/Amsterdam"`)
tk.MustExec(`set sql_mode = ''`)
// This will be adjusted to '2025-03-30 03:00:00+02:00'
tk.MustExec("insert into t values (4, '2025-03-30 02:30:00')")
tk.MustQuery(`show warnings`).Check(testkit.Rows("Warning 1292 Incorrect timestamp value: '2025-03-30 02:30:00' for column 'ts' at row 1"))
tk.MustExec(`set sql_mode = DEFAULT`)
tk.MustQuery(`select *, unix_timestamp(ts) from t`).Sort().Check(testkit.Rows(""+
"1 1970-01-01 01:00:01 1",
"2 2025-03-30 01:59:59 1743296399",
"3 2025-03-30 03:00:00 1743296400",
"4 2025-03-30 03:00:00 1743296400"))

// Compares as DATETIME; every row is read and converted to DATETIME by current TIME_ZONE,
// and compared with the range which is in DATETIME
tk.MustQuery(`select *, unix_timestamp(ts) from t where ts between '2025-03-30 02:30:00' AND '2025-03-30 03:00:00'`).Check(testkit.Rows("3 2025-03-30 03:00:00 1743296400", "4 2025-03-30 03:00:00 1743296400"))
tk.MustQuery(`show warnings`).Sort().Check(testkit.Rows("Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 30 0 0}' for time zone 'Europe/Amsterdam'"))
explain := tk.MustQuery(`explain select *, unix_timestamp(ts) from t where ts between '2025-03-30 02:30:00' AND '2025-03-30 03:00:00'`)
explain.MultiCheckContain([]string{"TableFullScan", "ge(test.t.ts, 2025-03-30 02:30:00.000000)", "le(test.t.ts, 2025-03-30 03:00:00.000000)"})

// Compares as TIMESTAMP; the range is converted to TIMESTAMP by current TIME_ZONE,
// and then compared with the row which is TIMESTAMP.
tk.MustExec("alter table t add index idx_ts(ts)")
tk.MustQuery(`select *, unix_timestamp(ts) from t where ts between '2025-03-30 02:30:00' AND '2025-03-30 03:00:00'`).Check(testkit.Rows("3 2025-03-30 03:00:00 1743296400", "4 2025-03-30 03:00:00 1743296400"))
explain = tk.MustQuery(`explain select *, unix_timestamp(ts) from t where ts between '2025-03-30 02:30:00' AND '2025-03-30 03:00:00'`)
explain.MultiCheckContain([]string{"IndexLookUp", "range:[2025-03-30 03:00:00,2025-03-30 03:00:00]"})
explain.CheckNotContain("02:30:00")
// Why 3 warnings?!?
tk.MustQuery(`show warnings`).Check(testkit.Rows("Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 30 0 0}' for time zone 'Europe/Amsterdam'",
"Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 30 0 0}' for time zone 'Europe/Amsterdam'",
"Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 30 0 0}' for time zone 'Europe/Amsterdam'"))
}
41 changes: 31 additions & 10 deletions pkg/types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,13 @@ func (t Time) Convert(ctx Context, tp uint8) (Time, error) {

t1.SetType(tp)
err := t1.Check(ctx)
if tp == mysql.TypeTimestamp && ErrTimestampInDSTTransition.Equal(err) {
tAdj, adjErr := t1.AdjustedGoTime(ctx.Location())
if adjErr == nil {
ctx.AppendWarning(err)
return Time{FromGoTime(tAdj)}, nil
}
}
return t1, errors.Trace(err)
}

Expand Down Expand Up @@ -2007,22 +2014,35 @@ func parseTime(ctx Context, str string, tp byte, fsp int, isFloat bool) (Time, e

t.SetType(tp)
if err = t.Check(ctx); err != nil {
minTS, maxTS := MinTimestamp, MaxTimestamp
minErr := minTS.ConvertTimeZone(gotime.UTC, ctx.Location())
maxErr := maxTS.ConvertTimeZone(gotime.UTC, ctx.Location())
if minErr == nil && maxErr == nil && tp == mysql.TypeTimestamp && !t.IsZero() &&
t.Compare(minTS) > 0 && t.Compare(maxTS) < 0 {
// Handle the case when the timestamp given is in the DST transition
if tAdjusted, err2 := t.AdjustedGoTime(ctx.Location()); err2 == nil {
t.SetCoreTime(FromGoTime(tAdjusted))
return t, errors.Trace(ErrTimestampInDSTTransition.GenWithStackByArgs(str, ctx.Location().String()))
if tp == mysql.TypeTimestamp && !t.IsZero() {
tAdjusted, errAdjusted := adjustTimestampErrForDST(ctx.Location(), str, tp, t, err)
if ErrTimestampInDSTTransition.Equal(errAdjusted) {
return tAdjusted, errors.Trace(errAdjusted)
}
}
return NewTime(ZeroCoreTime, tp, DefaultFsp), errors.Trace(err)
}
return t, nil
}

func adjustTimestampErrForDST(loc *gotime.Location, str string, tp byte, t Time, err error) (Time, error) {
if tp != mysql.TypeTimestamp || t.IsZero() {
return t, err
}
minTS, maxTS := MinTimestamp, MaxTimestamp
minErr := minTS.ConvertTimeZone(gotime.UTC, loc)
maxErr := maxTS.ConvertTimeZone(gotime.UTC, loc)
if minErr == nil && maxErr == nil &&
t.Compare(minTS) > 0 && t.Compare(maxTS) < 0 {
// Handle the case when the timestamp given is in the DST transition
if tAdjusted, err2 := t.AdjustedGoTime(loc); err2 == nil {
t.SetCoreTime(FromGoTime(tAdjusted))
return t, errors.Trace(ErrTimestampInDSTTransition.GenWithStackByArgs(str, loc.String()))
}
}
return t, err
}

// ParseDatetime is a helper function wrapping ParseTime with datetime type and default fsp.
func ParseDatetime(ctx Context, str string) (Time, error) {
return ParseTime(ctx, str, mysql.TypeDatetime, GetFsp(str))
Expand Down Expand Up @@ -2179,7 +2199,8 @@ func checkTimestampType(t CoreTime, tz *gotime.Location) error {
convertTime := NewTime(t, mysql.TypeTimestamp, DefaultFsp)
err := convertTime.ConvertTimeZone(tz, BoundTimezone)
if err != nil {
return err
_, err2 := adjustTimestampErrForDST(tz, t.String(), mysql.TypeTimestamp, Time{t}, err)
return err2
}
checkTime = convertTime.coreTime
} else {
Expand Down
142 changes: 142 additions & 0 deletions tests/integrationtest/r/types/time.result
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,145 @@ SELECT { t '14:00:00' };
14:00:00
SELECT { d '2024-01-01 01:12:31' };
Error 1292 (22007): Incorrect date value: '2024-01-01 01:12:31'
SET time_zone = 'UTC';
CREATE TABLE t (id int primary key, ts TIMESTAMP);
INSERT INTO t VALUES (1, '2025-03-30 00:59:59');
INSERT INTO t VALUES (2, '2025-03-30 01:00:00');
SET time_zone = 'Europe/Paris';
SELECT *, UNIX_TIMESTAMP(ts) FROM t;
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
2 2025-03-30 03:00:00 1743296400
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts < '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts <= '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts > '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
2 2025-03-30 03:00:00 1743296400
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts >= '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
2 2025-03-30 03:00:00 1743296400
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts <=> '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE date_add(ts, interval 20 minute) = '2025-03-30 02:19:59';
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE date_add(ts, interval -20 minute) = '2025-03-30 02:40:00';
id ts UNIX_TIMESTAMP(ts)
2 2025-03-30 03:00:00 1743296400
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = date_add('2025-03-30 02:40:00', interval 20 minute);
id ts UNIX_TIMESTAMP(ts)
2 2025-03-30 03:00:00 1743296400
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = date_add('2025-03-30 02:19:59', interval -20 minute);
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
ALTER TABLE t ADD INDEX idx_ts (ts);
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts < '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts <= '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts > '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
2 2025-03-30 03:00:00 1743296400
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts >= '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
2 2025-03-30 03:00:00 1743296400
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts <=> '2025-03-30 02:00:00';
id ts UNIX_TIMESTAMP(ts)
Level Code Message
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 0 0 0}' for time zone 'Europe/Paris'
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE date_add(ts, interval 20 minute) = '2025-03-30 02:19:59';
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE date_add(ts, interval -20 minute) = '2025-03-30 02:40:00';
id ts UNIX_TIMESTAMP(ts)
2 2025-03-30 03:00:00 1743296400
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = date_add('2025-03-30 02:40:00', interval 20 minute);
id ts UNIX_TIMESTAMP(ts)
2 2025-03-30 03:00:00 1743296400
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = date_add('2025-03-30 02:19:59', interval -20 minute);
id ts UNIX_TIMESTAMP(ts)
1 2025-03-30 01:59:59 1743296399
SELECT DATE_ADD(ts, INTERVAL 1 HOUR) FROM t;
DATE_ADD(ts, INTERVAL 1 HOUR)
2025-03-30 02:59:59
2025-03-30 04:00:00
SELECT DATE_SUB(ts, INTERVAL 1 HOUR) FROM t;
DATE_SUB(ts, INTERVAL 1 HOUR)
2025-03-30 00:59:59
2025-03-30 02:00:00
SELECT TIMESTAMPADD(HOUR, 1, ts) FROM t;
Error 8179 (HY000): Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 59 59 0}' for time zone 'Europe/Paris'
SELECT TIMESTAMPDIFF(HOUR, '2025-03-30 01:59:59', ts) FROM t;
TIMESTAMPDIFF(HOUR, '2025-03-30 01:59:59', ts)
0
1
SELECT UNIX_TIMESTAMP(ts) FROM t;
UNIX_TIMESTAMP(ts)
1743296399
1743296400
SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(ts)) FROM t;
FROM_UNIXTIME(UNIX_TIMESTAMP(ts))
2025-03-30 01:59:59
2025-03-30 03:00:00
SELECT DATE_ADD('2025-03-30 02:30:00', INTERVAL 1 HOUR);
DATE_ADD('2025-03-30 02:30:00', INTERVAL 1 HOUR)
2025-03-30 03:30:00
SELECT DATE_SUB('2025-03-30 02:30:00', INTERVAL 1 HOUR);
DATE_SUB('2025-03-30 02:30:00', INTERVAL 1 HOUR)
2025-03-30 01:30:00
SELECT TIMESTAMPADD(HOUR, 1, '2025-03-30 02:30:00');
TIMESTAMPADD(HOUR, 1, '2025-03-30 02:30:00')
2025-03-30 03:30:00
SELECT TIMESTAMPDIFF(HOUR, '2025-03-30 01:59:59', '2025-03-30 02:30:00');
TIMESTAMPDIFF(HOUR, '2025-03-30 01:59:59', '2025-03-30 02:30:00')
0
SELECT UNIX_TIMESTAMP('2025-03-30 02:30:00');
UNIX_TIMESTAMP('2025-03-30 02:30:00')
1743296400
SELECT FROM_UNIXTIME(UNIX_TIMESTAMP('2025-03-30 02:30:00'));
FROM_UNIXTIME(UNIX_TIMESTAMP('2025-03-30 02:30:00'))
2025-03-30 03:00:00
51 changes: 51 additions & 0 deletions tests/integrationtest/t/types/time.test
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,54 @@ SELECT { t '14:00:00' };
SELECT { d '2024-01-01 01:12:31' };

# TODO: Also test if there are difference between explicitly give the timestamp/datetime as literal/string or having it from a varchar in a table

SET time_zone = 'UTC';
CREATE TABLE t (id int primary key, ts TIMESTAMP);
INSERT INTO t VALUES (1, '2025-03-30 00:59:59');
INSERT INTO t VALUES (2, '2025-03-30 01:00:00');
SET time_zone = 'Europe/Paris';
SELECT *, UNIX_TIMESTAMP(ts) FROM t;
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = '2025-03-30 02:00:00';
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts < '2025-03-30 02:00:00';
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts <= '2025-03-30 02:00:00';
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts > '2025-03-30 02:00:00';
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts >= '2025-03-30 02:00:00';
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts <=> '2025-03-30 02:00:00';
# All these probably converts to DATETIME (including tz conversions) before comparing
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE date_add(ts, interval 20 minute) = '2025-03-30 02:19:59';
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE date_add(ts, interval -20 minute) = '2025-03-30 02:40:00';
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = date_add('2025-03-30 02:40:00', interval 20 minute);
SELECT *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = date_add('2025-03-30 02:19:59', interval -20 minute);

ALTER TABLE t ADD INDEX idx_ts (ts);
# Note: MySQL would return 03:00:00 on '=', different from not being indexed!
# reference: https://bugs.mysql.com/bug.php?id=38455
# TiDB is better, by being consistent regardless if indexed or not!
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = '2025-03-30 02:00:00';
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts < '2025-03-30 02:00:00';
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts <= '2025-03-30 02:00:00';
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts > '2025-03-30 02:00:00';
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts >= '2025-03-30 02:00:00';
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts <=> '2025-03-30 02:00:00';
# All these probably converts to DATETIME (including tz conversions) before comparing
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE date_add(ts, interval 20 minute) = '2025-03-30 02:19:59';
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE date_add(ts, interval -20 minute) = '2025-03-30 02:40:00';
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = date_add('2025-03-30 02:40:00', interval 20 minute);
SELECT /*+ USE_INDEX(t, idx_ts) */ *, UNIX_TIMESTAMP(ts) FROM t WHERE ts = date_add('2025-03-30 02:19:59', interval -20 minute);

SELECT DATE_ADD(ts, INTERVAL 1 HOUR) FROM t;
SELECT DATE_SUB(ts, INTERVAL 1 HOUR) FROM t;
# If not error, his returns NULL, but MySQL returns 02:59:59.
# TODO: issue#61567, also understand when it returns NULL and when it gives error!
--error 8179
SELECT TIMESTAMPADD(HOUR, 1, ts) FROM t;
SELECT TIMESTAMPDIFF(HOUR, '2025-03-30 01:59:59', ts) FROM t;
SELECT UNIX_TIMESTAMP(ts) FROM t;
SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(ts)) FROM t;

SELECT DATE_ADD('2025-03-30 02:30:00', INTERVAL 1 HOUR);
SELECT DATE_SUB('2025-03-30 02:30:00', INTERVAL 1 HOUR);
SELECT TIMESTAMPADD(HOUR, 1, '2025-03-30 02:30:00');
SELECT TIMESTAMPDIFF(HOUR, '2025-03-30 01:59:59', '2025-03-30 02:30:00');
SELECT UNIX_TIMESTAMP('2025-03-30 02:30:00');
SELECT FROM_UNIXTIME(UNIX_TIMESTAMP('2025-03-30 02:30:00'));