diff --git a/pkg/sql/logictest/testdata/logic_test/time b/pkg/sql/logictest/testdata/logic_test/time index 4abbf001e9b3..7b2c6a9bf6dc 100644 --- a/pkg/sql/logictest/testdata/logic_test/time +++ b/pkg/sql/logictest/testdata/logic_test/time @@ -23,8 +23,25 @@ SELECT '23:59:59.999999':::TIME; ---- 0000-01-01 23:59:59.999999 +0000 UTC +query T +select ('24:00'::TIME)::STRING +---- +24:00:00 + +query T +SELECT ('24:00:00'::TIME)::STRING +---- +24:00:00 + statement error could not parse -SELECT '24:00:00':::TIME; +SELECT '124:00'::TIME; + +statement error could not parse +SELECT '24:00:01'::TIME; + +statement error could not parse +SELECT '24:00:00.001'::TIME; + # Timezone should be ignored. query T diff --git a/pkg/sql/sem/tree/datum.go b/pkg/sql/sem/tree/datum.go index 4cc771e272b9..6cab07ef4780 100644 --- a/pkg/sql/sem/tree/datum.go +++ b/pkg/sql/sem/tree/datum.go @@ -20,6 +20,7 @@ import ( "math" "math/big" "net" + "regexp" "sort" "strconv" "strings" @@ -64,6 +65,9 @@ var ( // DZero is the zero-valued integer Datum. DZero = NewDInt(0) + + // DTimeRegex is a compiled regex for parsing the 24:00 time value + DTimeRegex = regexp.MustCompile("^24:00($|(:00$)|(:00.0+$))") ) // Datum represents a SQL value. @@ -1817,6 +1821,13 @@ func MakeDTime(t timeofday.TimeOfDay) *DTime { // provided string, or an error if parsing is unsuccessful. func ParseDTime(ctx ParseTimeContext, s string) (*DTime, error) { now := relativeParseTime(ctx) + + // special case on 24:00 and 24:00:00 as the parser + // does not handle these correctly. + if DTimeRegex.MatchString(s) { + return MakeDTime(timeofday.Time2400), nil + } + t, err := pgdate.ParseTime(now, 0 /* mode */, s) if err != nil { // Build our own error message to avoid exposing the dummy date. diff --git a/pkg/sql/sem/tree/datum_test.go b/pkg/sql/sem/tree/datum_test.go index 21bce9b6e34d..b914b5e1b40d 100644 --- a/pkg/sql/sem/tree/datum_test.go +++ b/pkg/sql/sem/tree/datum_test.go @@ -105,6 +105,8 @@ func TestDatumOrdering(t *testing.T) { `'00:00:00'`, `'23:59:59.999999'`}, {`'23:59:59.999999':::time`, `'23:59:59.999998'`, valIsMax, `'00:00:00'`, `'23:59:59.999999'`}, + {`'24:00':::time`, `'23:59:59.999999'`, `'00:00:00.000001'`, + `'00:00:00'`, `'23:59:59.999999'`}, // Intervals {`'1 day':::interval`, noPrev, noNext, @@ -484,6 +486,9 @@ func TestParseDTime(t *testing.T) { {"04:05:06.000001", timeofday.New(4, 5, 6, 1)}, {"04:05:06-07", timeofday.New(4, 5, 6, 0)}, {"4:5:6", timeofday.New(4, 5, 6, 0)}, + {"24:00:00", timeofday.Time2400}, + {"24:00:00.000", timeofday.Time2400}, + {"24:00:00.000000", timeofday.Time2400}, } for _, td := range testData { actual, err := tree.ParseDTime(nil, td.str) @@ -502,7 +507,6 @@ func TestParseDTimeError(t *testing.T) { "", "foo", "01", - "24:00:00", } for _, s := range testData { actual, _ := tree.ParseDTime(nil, s) diff --git a/pkg/util/timeofday/time_of_day.go b/pkg/util/timeofday/time_of_day.go index f53fcef429a1..1156d7df4738 100644 --- a/pkg/util/timeofday/time_of_day.go +++ b/pkg/util/timeofday/time_of_day.go @@ -31,9 +31,13 @@ type TimeOfDay int64 const ( // Min is the minimum TimeOfDay value (midnight). Min = TimeOfDay(0) - // Max is the maximum TimeOfDay value (1 microsecond before midnight). + + // Max is the maximum TimeOfDay value (1 second before midnight) Max = TimeOfDay(microsecondsPerDay - 1) + // Time2400 is a special value to represent the 24:00 input time + Time2400 = TimeOfDay(microsecondsPerDay) + microsecondsPerSecond = 1e6 microsecondsPerMinute = 60 * microsecondsPerSecond microsecondsPerHour = 60 * microsecondsPerMinute @@ -107,8 +111,11 @@ func Difference(t1 TimeOfDay, t2 TimeOfDay) duration.Duration { return duration.MakeDuration(int64(t1-t2)*nanosPerMicro, 0, 0) } -// Hour returns the hour specified by t, in the range [0, 23]. +// Hour returns the hour specified by t, in the range [0, 24]. func (t TimeOfDay) Hour() int { + if t == Time2400 { + return 24 + } return int(int64(t)%microsecondsPerDay) / microsecondsPerHour }