From 49289dc1b79040f3643e9fe02802a751bdfef287 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Fri, 15 Mar 2024 23:24:07 +0530 Subject: [PATCH] Add required tests for go/mysql/datetime Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime_test.go | 850 ++++++++++++++++++++++++++++- go/mysql/datetime/interval_test.go | 373 +++++++++++++ go/mysql/datetime/mydate_test.go | 8 + go/mysql/datetime/parse_test.go | 254 +++++++++ go/mysql/datetime/strftime_test.go | 83 +++ 5 files changed, 1557 insertions(+), 11 deletions(-) create mode 100644 go/mysql/datetime/interval_test.go create mode 100644 go/mysql/datetime/strftime_test.go diff --git a/go/mysql/datetime/datetime_test.go b/go/mysql/datetime/datetime_test.go index 7a1c0952fac..da2aec48431 100644 --- a/go/mysql/datetime/datetime_test.go +++ b/go/mysql/datetime/datetime_test.go @@ -21,9 +21,11 @@ import ( "time" "github.com/stretchr/testify/assert" + + "vitess.io/vitess/go/mysql/decimal" ) -var testGoTime = time.Date(2024, 03, 12, 12, 30, 20, 59, time.UTC) +var testGoTime = time.Date(2024, 03, 12, 12, 30, 20, 987654321, time.UTC) func TestNewTimeFromStd(t *testing.T) { time := NewTimeFromStd(testGoTime) @@ -31,7 +33,7 @@ func TestNewTimeFromStd(t *testing.T) { assert.Equal(t, uint16(12), time.hour) assert.Equal(t, uint8(30), time.minute) assert.Equal(t, uint8(20), time.second) - assert.Equal(t, uint32(59), time.nanosecond) + assert.Equal(t, uint32(987654321), time.nanosecond) } func TestNewDateFromStd(t *testing.T) { @@ -52,22 +54,848 @@ func TestNewDateTimeFromStd(t *testing.T) { assert.Equal(t, uint16(12), dt.Time.hour) assert.Equal(t, uint8(30), dt.Time.minute) assert.Equal(t, uint8(20), dt.Time.second) - assert.Equal(t, uint32(59), dt.Time.nanosecond) + assert.Equal(t, uint32(987654321), dt.Time.nanosecond) } func TestAppendFormat(t *testing.T) { time := NewTimeFromStd(testGoTime) + b := []byte("test AppendFormat: ") + + testCases := []struct { + prec uint8 + want []byte + }{ + {0, []byte("test AppendFormat: 12:30:20")}, + {1, []byte("test AppendFormat: 12:30:20.9")}, + {3, []byte("test AppendFormat: 12:30:20.987")}, + } - b := []byte("test:") + for _, tc := range testCases { + nb := time.AppendFormat(b, tc.prec) + assert.Equal(t, tc.want, nb) + } + + // Neg-time case + time = Time{ + hour: 1<<15 + 12, + minute: 30, + second: 20, + } nb := time.AppendFormat(b, 0) - assert.Equal(t, []byte("test:12:30:20"), nb) + assert.Equal(t, []byte("test AppendFormat: -12:30:20"), nb) +} + +func TestFormat(t *testing.T) { + time := NewTimeFromStd(testGoTime) + + testCases := []struct { + prec uint8 + want []byte + }{ + {0, []byte("12:30:20")}, + {1, []byte("12:30:20.9")}, + {3, []byte("12:30:20.987")}, + } + + for _, tc := range testCases { + nb := time.Format(tc.prec) + assert.Equal(t, tc.want, nb) + } + + // Neg-time case + time = Time{ + hour: 1<<15 + 12, + minute: 30, + second: 20, + } + nb := time.Format(0) + assert.Equal(t, []byte("-12:30:20"), nb) +} + +func TestFormats(t *testing.T) { + testCases := []struct { + hour uint16 + minute uint8 + second uint8 + nanosecond uint32 + wantInt64 int64 + wantFloat64 float64 + wantDecimal decimal.Decimal + }{ + { + hour: 12, + minute: 30, + second: 20, + nanosecond: 987654321, + wantInt64: 123021, + wantFloat64: 123020.987654321, + wantDecimal: decimal.NewFromFloat(123020.987654321), + }, + { + hour: 1<<15 + 12, + minute: 30, + second: 20, + nanosecond: 987654321, + wantInt64: -123021, + wantFloat64: -123020.987654321, + wantDecimal: decimal.NewFromFloat(-123020.987654321), + }, + { + hour: 1<<15 + 123, + minute: 9, + second: 9, + nanosecond: 123456789, + wantInt64: -1230909, + wantFloat64: -1230909.123456789, + wantDecimal: decimal.NewFromFloat(-1230909.123456789), + }, + } + + for _, tc := range testCases { + time := Time{ + hour: tc.hour, + minute: tc.minute, + second: tc.second, + nanosecond: tc.nanosecond, + } + + n := time.FormatInt64() + assert.Equal(t, tc.wantInt64, n) + + f := time.FormatFloat64() + assert.Equal(t, tc.wantFloat64, f) + + d := time.FormatDecimal() + assert.Equal(t, tc.wantDecimal, d) + } +} + +func TestToDateTime(t *testing.T) { + time := NewTimeFromStd(testGoTime) + + want := DateTime{ + Date: Date{ + year: 2024, + month: 3, + day: 12, + }, + Time: Time{ + hour: 12, + minute: 30, + second: 20, + nanosecond: 987654321, + }, + } + + got := time.ToDateTime(testGoTime) + + assert.Equal(t, want, got) +} + +func TestTimeIsZero(t *testing.T) { + testCases := []struct { + hour uint16 + minute uint8 + second uint8 + nanosecond uint32 + wantZero bool + }{ + {0, 0, 0, 0, true}, + {0, 0, 0, 123, false}, + {12, 12, 23, 0, false}, + } + + for _, tc := range testCases { + time := Time{ + hour: tc.hour, + minute: tc.minute, + second: tc.second, + nanosecond: tc.nanosecond, + } + + z := time.IsZero() + if tc.wantZero { + assert.True(t, z, "Time %v should be considered as zero time", time) + } else { + assert.False(t, z, "Time %v should not be considered as zero time", time) + } + } +} + +func TestRoundForJSON(t *testing.T) { + testCases := []struct { + hour uint16 + minute uint8 + second uint8 + nanosecond uint32 + want Time + }{ + { + hour: 12, + minute: 30, + second: 20, + nanosecond: 987654321, + want: Time{12, 30, 20, 987654321}, + }, + { + hour: 1<<15 + 123, + minute: 9, + second: 9, + nanosecond: 123456789, + want: Time{1<<15 + 27, 9, 9, 123456789}, + }, + } + + for _, tc := range testCases { + time := Time{ + hour: tc.hour, + minute: tc.minute, + second: tc.second, + nanosecond: tc.nanosecond, + } + + res := time.RoundForJSON() + assert.Equal(t, tc.want, res) + } +} + +func TestCompare(t *testing.T) { + time := NewTimeFromStd(testGoTime) + + testCases := []struct { + hour uint16 + minute uint8 + second uint8 + nanosecond uint32 + want int + }{ + {12, 30, 20, 987654321, 0}, + {1<<15 + 12, 30, 20, 987654321, 1}, + {12, 29, 20, 987654321, 1}, + {12, 31, 20, 987654321, -1}, + {13, 30, 20, 987654321, -1}, + {11, 30, 20, 987654321, 1}, + {12, 30, 19, 98765, 1}, + {12, 30, 21, 98765432, -1}, + {12, 30, 20, 123123231, 1}, + {12, 30, 20, 987654322, -1}, + {12, 30, 20, 987654322, -1}, + } + + for _, tc := range testCases { + t2 := Time{ + hour: tc.hour, + minute: tc.minute, + second: tc.second, + nanosecond: tc.nanosecond, + } + + res := time.Compare(t2) + assert.Equal(t, tc.want, res) + + // If we use `t2` to call Compare, then result should be negative + // of what we wanted when `time` was used to call Compare + res = t2.Compare(time) + assert.Equal(t, -tc.want, res) + } + + // Case when both Time are negative + time = Time{ + hour: 1<<15 + 12, + minute: 30, + second: 20, + nanosecond: 987654321, + } + t2 := Time{ + hour: 1<<15 + 13, + minute: 30, + second: 20, + nanosecond: 987654321, + } + res := time.Compare(t2) + assert.Equal(t, 1, res) +} + +func TestRound(t *testing.T) { + time := NewTimeFromStd(testGoTime) + + testCases := []struct { + time Time + round int + want Time + }{ + { + time: time, + round: 9, + want: time, + }, + { + time: time, + round: 5, + want: Time{ + hour: 12, + minute: 30, + second: 20, + nanosecond: 987650000, + }, + }, + { + time: time, + round: 0, + want: Time{ + hour: 12, + minute: 30, + second: 21, + nanosecond: 0, + }, + }, + { + time: Time{ + hour: 12, + minute: 30, + second: 20, + }, + round: 0, + want: Time{ + hour: 12, + minute: 30, + second: 20, + }, + }, + { + time: Time{ + hour: 12, + minute: 59, + second: 59, + nanosecond: 987654321, + }, + round: 0, + want: Time{ + hour: 13, + minute: 0, + second: 0, + nanosecond: 0, + }, + }, + } + + for _, tc := range testCases { + res := tc.time.Round(tc.round) + + assert.Equal(t, tc.want, res) + } +} + +func TestDateIsZero(t *testing.T) { + testCases := []struct { + year uint16 + month uint8 + day uint8 + wantZero bool + }{ + {0, 0, 0, true}, + {0, 0, 1, false}, + {2023, 12, 23, false}, + } + + for _, tc := range testCases { + date := Date{ + year: tc.year, + month: tc.month, + day: tc.day, + } + + z := date.IsZero() + if tc.wantZero { + assert.True(t, z, "Date %v should be considered as zero date", date) + } else { + assert.False(t, z, "Date %v should not be considered as zero date", date) + } + } +} + +func TestWeekday(t *testing.T) { + testCases := []struct { + year uint16 + month uint8 + day uint8 + want int + }{ + {0, 1, 1, 0}, + {0, 2, 28, 2}, + {0, 3, 1, 3}, + {2024, 3, 13, 3}, + } + + for _, tc := range testCases { + date := Date{ + year: tc.year, + month: tc.month, + day: tc.day, + } + + wd := date.Weekday() + assert.Equal(t, time.Weekday(tc.want), wd) + } +} + +func TestMondayWeekAndSunday4DayWeek(t *testing.T) { + testCases := []struct { + year uint16 + month uint8 + day uint8 + wantWeekDay int + wantYear int + }{ + {0, 1, 1, 52, -1}, + {0, 2, 28, 9, 0}, + {0, 3, 1, 9, 0}, + {2024, 3, 13, 11, 2024}, + } - nb = time.AppendFormat(b, 1) - assert.Equal(t, []byte("test:12:30:20.0"), nb) + for _, tc := range testCases { + date := Date{ + year: tc.year, + month: tc.month, + day: tc.day, + } - nb = time.AppendFormat(b, 3) - assert.Equal(t, []byte("test:12:30:20.000"), nb) + y, wd := date.MondayWeek() + assert.Equal(t, tc.wantWeekDay, wd) + assert.Equal(t, tc.wantYear, y) - // TODO: Add tests for non-zero decimal part - // TODO: Add tests for neg hour + y, wd = date.Sunday4DayWeek() + assert.Equal(t, tc.wantWeekDay, wd) + assert.Equal(t, tc.wantYear, y) + } } + +func TestWeek(t *testing.T) { + testCases := []struct { + year uint16 + month uint8 + day uint8 + mode int + want int + }{ + {0, 1, 1, 0, 0}, + {0, 1, 1, 1, 0}, + {0, 2, 28, 2, 9}, + {0, 3, 1, 3, 9}, + {2001, 3, 14, 4, 11}, + {2000, 7, 12, 5, 28}, + {2024, 3, 13, 6, 11}, + {2024, 3, 13, 7, 11}, + } + + for _, tc := range testCases { + date := Date{ + year: tc.year, + month: tc.month, + day: tc.day, + } + + wd := date.Week(tc.mode) + assert.Equal(t, tc.want, wd) + } +} + +func TestYearWeek(t *testing.T) { + testCases := []struct { + year uint16 + month uint8 + day uint8 + mode int + want int + }{ + {0, 1, 1, 0, -48}, + {0, 1, 1, 1, -48}, + {0, 2, 28, 2, 9}, + {0, 3, 1, 3, 9}, + {2001, 3, 14, 4, 200111}, + {2000, 7, 12, 5, 200028}, + {2024, 3, 13, 6, 202411}, + {2024, 3, 13, 7, 202411}, + {2024, 3, 13, 8, 202410}, + } + + for _, tc := range testCases { + date := Date{ + year: tc.year, + month: tc.month, + day: tc.day, + } + + wd := date.YearWeek(tc.mode) + assert.Equal(t, tc.want, wd) + } +} + +func TestToDuration(t *testing.T) { + tt := NewTimeFromStd(testGoTime) + + res := tt.ToDuration() + assert.Equal(t, 45020987654321, int(res)) + + // Neg Time Case + tt.hour = 1<<15 | tt.hour + res = tt.ToDuration() + + assert.Equal(t, -45020987654321, int(res)) +} + +func TestToSeconds(t *testing.T) { + tt := NewTimeFromStd(testGoTime) + + res := tt.ToSeconds() + assert.Equal(t, 45020, int(res)) + + // Neg Time Case + tt.hour = 1<<15 | tt.hour + res = tt.ToSeconds() + + assert.Equal(t, -45020, int(res)) +} + +func TestToStdTime(t *testing.T) { + testCases := []struct { + year int + month int + day int + hour int + minute int + second int + nanosecond int + }{ + {2024, 3, 15, 12, 23, 34, 45}, + {2024, 3, 15, 0, 0, 0, 0}, + {0, 0, 0, 12, 23, 34, 45}, + {0, 0, 0, 0, 0, 0, 0}, + } + + for _, tc := range testCases { + dt := DateTime{ + Date: Date{uint16(tc.year), uint8(tc.month), uint8(tc.day)}, + Time: Time{uint16(tc.hour), uint8(tc.minute), uint8(tc.second), uint32(tc.nanosecond)}, + } + + res := dt.ToStdTime(time.Now()) + + if dt.IsZero() { + assert.Equal(t, 1, res.Day()) + assert.Equal(t, time.Month(1), res.Month()) + assert.Equal(t, 1, res.Year()) + } else if dt.Date.IsZero() { + assert.Equal(t, time.Now().Day(), res.Day()) + assert.Equal(t, time.Now().Month(), res.Month()) + assert.Equal(t, time.Now().Year(), res.Year()) + } else { + assert.Equal(t, tc.day, res.Day()) + assert.Equal(t, time.Month(tc.month), res.Month()) + assert.Equal(t, tc.year, res.Year()) + } + + assert.Equal(t, tc.hour, res.Hour()) + assert.Equal(t, tc.minute, res.Minute()) + assert.Equal(t, tc.second, res.Second()) + assert.Equal(t, tc.nanosecond, res.Nanosecond()) + } +} + +func TestDateFormats(t *testing.T) { + testCases := []struct { + year int + month int + day int + hour int + minute int + second int + nanosecond int + wantDate string + wantDateInt64 int64 + }{ + {2024, 3, 15, 12, 23, 34, 45, "2024-03-15", 20240315}, + {2024, 3, 15, 0, 0, 0, 0, "2024-03-15", 20240315}, + {0, 0, 0, 12, 23, 34, 45000, "0000-00-00", 0}, + {0, 0, 0, 0, 0, 0, 0, "0000-00-00", 0}, + } + + for _, tc := range testCases { + d := Date{uint16(tc.year), uint8(tc.month), uint8(tc.day)} + + b := d.Format() + assert.Equal(t, tc.wantDate, string(b)) + + f := d.FormatInt64() + assert.Equal(t, tc.wantDateInt64, f) + } +} + +func TestDateCompare(t *testing.T) { + testCases := []struct { + d1 Date + d2 Date + want int + }{ + {Date{2024, 03, 12}, Date{2023, 02, 28}, 1}, + {Date{2023, 02, 28}, Date{2024, 03, 12}, -1}, + {Date{2024, 03, 12}, Date{2024, 02, 28}, 1}, + {Date{2024, 02, 28}, Date{2024, 03, 12}, -1}, + {Date{2024, 02, 28}, Date{2024, 02, 12}, 1}, + {Date{2024, 02, 12}, Date{2024, 02, 28}, -1}, + {Date{2024, 03, 12}, Date{2024, 03, 12}, 0}, + } + + for _, tc := range testCases { + got := tc.d1.Compare(tc.d2) + assert.Equal(t, tc.want, got) + } +} + +func TestAddInterval(t *testing.T) { + testCases := []struct { + d Date + in Interval + want Date + ok bool + }{ + { + d: Date{2024, 03, 12}, + in: Interval{ + timeparts: timeparts{ + sec: (maxDay + 1) * 24 * 60 * 60, + prec: 6, + }, + unit: IntervalSecond, + }, + want: Date{2024, 03, 12}, + ok: false, + }, + { + d: Date{2023, 02, 12}, + in: Interval{ + timeparts: timeparts{ + day: 18, + sec: 12, + prec: 6, + }, + unit: IntervalSecond, + }, + want: Date{2023, 03, 02}, + ok: true, + }, + { + d: Date{2024, 03, 12}, + in: Interval{ + timeparts: timeparts{ + sec: 3600 * 24, + prec: 6, + }, + unit: IntervalSecond, + }, + want: Date{2024, 03, 13}, + ok: true, + }, + { + d: Date{2024, 03, 12}, + in: Interval{ + timeparts: timeparts{ + day: maxDay + 1, + prec: 6, + }, + unit: IntervalDay, + }, + want: Date{0, 0, 0}, + ok: true, + }, + { + d: Date{2024, 03, 12}, + in: Interval{ + timeparts: timeparts{ + day: 123, + prec: 6, + }, + unit: IntervalDay, + }, + want: Date{2024, 7, 13}, + ok: true, + }, + { + d: Date{2024, 03, 12}, + in: Interval{ + timeparts: timeparts{ + month: 12, + }, + unit: IntervalMonth, + }, + want: Date{2025, 3, 12}, + ok: true, + }, + { + d: Date{2024, 03, 12}, + in: Interval{ + timeparts: timeparts{ + year: -3000, + }, + unit: IntervalMonth, + }, + want: Date{2024, 3, 12}, + ok: false, + }, + { + d: Date{2023, 03, 29}, + in: Interval{ + timeparts: timeparts{ + month: -1, + }, + unit: IntervalMonth, + }, + want: Date{2023, 2, 28}, + ok: true, + }, + { + d: Date{2024, 02, 29}, + in: Interval{ + timeparts: timeparts{ + year: -1, + }, + unit: IntervalYear, + }, + want: Date{2023, 2, 28}, + ok: true, + }, + { + d: Date{2024, 03, 12}, + in: Interval{ + timeparts: timeparts{ + year: 12, + }, + unit: IntervalYear, + }, + want: Date{2036, 3, 12}, + ok: true, + }, + { + d: Date{2024, 03, 12}, + in: Interval{ + timeparts: timeparts{ + year: 10001, + }, + unit: IntervalYear, + }, + want: Date{2024, 3, 12}, + ok: false, + }, + } + + for _, tc := range testCases { + d, ok := tc.d.AddInterval(&tc.in) + + assert.Equal(t, tc.want, d) + assert.Equal(t, tc.ok, ok) + } +} + +func TestWeightString(t *testing.T) { + testCases := []struct { + dt DateTime + want []byte + }{ + { + dt: DateTime{ + Date: Date{2024, 3, 15}, + Time: Time{7, 23, 40, 0}, + }, + want: []byte{116, 101, 115, 116, 58, 32, 153, 178, 222, 117, 232, 0, 0, 0}, + }, + { + dt: DateTime{ + Date: Date{2024, 3, 15}, + Time: Time{1<<15 | 7, 23, 40, 0}, + }, + want: []byte{116, 101, 115, 116, 58, 32, 102, 77, 33, 138, 24, 0, 0, 0}, + }, + } + + dst := []byte("test: ") + for _, tc := range testCases { + res := tc.dt.WeightString(dst) + assert.Equal(t, tc.want, res) + } +} + +func TestDateTimeFormats(t *testing.T) { + testCases := []struct { + year int + month int + day int + hour int + minute int + second int + nanosecond int + prec int + want string + wantDateTimeInt64 int64 + wantDateTimeFloat64 float64 + }{ + {2024, 3, 15, 12, 23, 34, 45, 0, "2024-03-15 12:23:34", 20240315122334, 20240315122334}, + {2024, 3, 15, 0, 0, 0, 0, 6, "2024-03-15 00:00:00.000000", 20240315000000, 20240315000000}, + {0, 0, 0, 12, 23, 34, 45000, 9, "0000-00-00 12:23:34.000045000", 122334, 122334.000045}, + {0, 0, 0, 0, 0, 0, 0, 0, "0000-00-00 00:00:00", 0, 0}, + } + + for _, tc := range testCases { + dt := DateTime{ + Date: Date{uint16(tc.year), uint8(tc.month), uint8(tc.day)}, + Time: Time{uint16(tc.hour), uint8(tc.minute), uint8(tc.second), uint32(tc.nanosecond)}, + } + + b := dt.Format(uint8(tc.prec)) + assert.Equal(t, tc.want, string(b)) + + i := dt.FormatInt64() + assert.Equal(t, tc.wantDateTimeInt64, i) + + f := dt.FormatFloat64() + assert.Equal(t, tc.wantDateTimeFloat64, f) + } +} + +func TestDateTimeCompare(t *testing.T) { + testCases := []struct { + dt1 DateTime + dt2 DateTime + want int + }{ + {DateTime{Date: Date{2024, 03, 12}}, DateTime{Date: Date{2024, 02, 12}}, 1}, + {DateTime{Time: Time{12, 30, 20, 0}}, DateTime{Time: Time{12, 30, 20, 23}}, -1}, + {DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 20, 0}}, DateTime{Time: Time{12, 30, 20, 23}}, -1}, + {DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 20, 0}}, DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 20, 0}}, 0}, + } + + for _, tc := range testCases { + got := tc.dt1.Compare(tc.dt2) + assert.Equal(t, tc.want, got) + } +} + +func TestDateTimeRound(t *testing.T) { + testCases := []struct { + dt DateTime + p int + want DateTime + }{ + {DateTime{Date: Date{2024, 03, 12}}, 4, DateTime{Date: Date{2024, 03, 12}}}, + {DateTime{Time: Time{12, 30, 20, 123312}}, 6, DateTime{Time: Time{12, 30, 20, 123000}}}, + {DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 20, 123312}}, 9, DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 20, 123312}}}, + {DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 20, 1e9}}, 9, DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 21, 0}}}, + {DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 20, 123}}, 0, DateTime{Date: Date{2024, 03, 12}, Time: Time{12, 30, 20, 0}}}, + } + + for _, tc := range testCases { + got := tc.dt.Round(tc.p) + assert.Equal(t, tc.want, got) + } +} + +// TODO: Write tests for Hash time/date diff --git a/go/mysql/datetime/interval_test.go b/go/mysql/datetime/interval_test.go new file mode 100644 index 00000000000..6ea6a444001 --- /dev/null +++ b/go/mysql/datetime/interval_test.go @@ -0,0 +1,373 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package datetime + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "vitess.io/vitess/go/mysql/decimal" +) + +func TestIntervalType(t *testing.T) { + testCases := []struct { + in IntervalType + wantPartCount int + wantTimeParts bool + wantDateParts bool + wantDayParts bool + wantMonthParts bool + wantNeedsPrecision bool + }{ + {IntervalYear, 1, false, true, false, false, false}, + {IntervalMonth, 1, false, true, false, true, false}, + {IntervalDay, 1, false, true, true, false, false}, + {IntervalHour, 1, true, false, false, false, false}, + {IntervalMinute, 1, true, false, false, false, false}, + {IntervalSecond, 1, true, false, false, false, false}, + {IntervalMicrosecond, 1, true, false, false, false, true}, + {IntervalNone, 0, false, false, false, false, false}, + {IntervalQuarter, 1, false, true, false, true, false}, + {IntervalWeek, 1, false, true, true, false, false}, + {IntervalSecondMicrosecond, 2, true, false, false, false, true}, + {IntervalMinuteMicrosecond, 3, true, false, false, false, true}, + {IntervalMinuteSecond, 2, true, false, false, false, false}, + {IntervalHourMicrosecond, 4, true, false, false, false, true}, + {IntervalHourSecond, 3, true, false, false, false, false}, + {IntervalHourMinute, 2, true, false, false, false, false}, + {IntervalDayMicrosecond, 5, true, true, true, false, true}, + {IntervalDaySecond, 4, true, true, true, false, false}, + {IntervalDayMinute, 3, true, true, true, false, false}, + {IntervalDayHour, 2, true, true, true, false, false}, + {IntervalYearMonth, 2, false, true, false, true, false}, + } + + for _, tc := range testCases { + got := tc.in.HasTimeParts() + assert.Equal(t, tc.wantTimeParts, got) + + got = tc.in.HasDateParts() + assert.Equal(t, tc.wantDateParts, got) + + got = tc.in.HasDayParts() + assert.Equal(t, tc.wantDayParts, got) + + got = tc.in.HasMonthParts() + assert.Equal(t, tc.wantMonthParts, got) + + got = tc.in.NeedsPrecision() + assert.Equal(t, tc.wantNeedsPrecision, got) + + assert.Equal(t, tc.wantPartCount, tc.in.PartCount()) + } +} + +func TestParseInterval(t *testing.T) { + testCases := []struct { + in string + tt IntervalType + want *Interval + }{ + { + in: "123", + tt: IntervalSecond, + want: &Interval{ + timeparts: timeparts{ + sec: 123, + prec: 6, + }, + unit: IntervalSecond, + }, + }, + { + in: "1", + tt: IntervalDay, + want: &Interval{ + timeparts: timeparts{ + day: 1, + prec: 0, + }, + unit: IntervalDay, + }, + }, + { + in: "1234", + tt: IntervalMinute, + want: &Interval{ + timeparts: timeparts{ + min: 1234, + prec: 0, + }, + unit: IntervalMinute, + }, + }, + { + in: "123.98", + tt: IntervalSecond, + want: &Interval{ + timeparts: timeparts{ + sec: 123, + nsec: 980000000, + prec: 6, + }, + unit: IntervalSecond, + }, + }, + } + + for _, tc := range testCases { + res := ParseInterval(tc.in, tc.tt, false) + assert.Equal(t, tc.want, res) + } + + // Neg interval case + res := ParseInterval("123", IntervalSecond, true) + want := &Interval{ + timeparts: timeparts{ + sec: -123, + prec: 6, + }, + unit: IntervalSecond, + } + assert.Equal(t, want, res) +} + +func TestParseIntervalInt64(t *testing.T) { + testCases := []struct { + in int64 + tt IntervalType + want *Interval + }{ + { + in: 123, + tt: IntervalSecond, + want: &Interval{ + timeparts: timeparts{ + sec: 123, + prec: 0, + }, + unit: IntervalSecond, + }, + }, + { + in: 1234, + tt: IntervalMicrosecond, + want: &Interval{ + timeparts: timeparts{ + nsec: 1234000, + prec: 6, + }, + unit: IntervalMicrosecond, + }, + }, + { + in: 35454, + tt: IntervalMinute, + want: &Interval{ + timeparts: timeparts{ + min: 35454, + prec: 0, + }, + unit: IntervalMinute, + }, + }, + } + + for _, tc := range testCases { + res := ParseIntervalInt64(tc.in, tc.tt, false) + assert.Equal(t, tc.want, res) + } + + // Neg interval case + res := ParseIntervalInt64(123, IntervalSecond, true) + want := &Interval{ + timeparts: timeparts{ + sec: -123, + prec: 0, + }, + unit: IntervalSecond, + } + assert.Equal(t, want, res) +} + +func TestParseIntervalFloat(t *testing.T) { + testCases := []struct { + in float64 + tt IntervalType + want *Interval + }{ + { + in: 123.45, + tt: IntervalSecond, + want: &Interval{ + timeparts: timeparts{ + sec: 123, + nsec: 450000000, + prec: 6, + }, + unit: IntervalSecond, + }, + }, + { + in: 12.34, + tt: IntervalMinute, + want: &Interval{ + timeparts: timeparts{ + min: 12, + prec: 0, + }, + unit: IntervalMinute, + }, + }, + { + in: 12.67, + tt: IntervalHour, + want: &Interval{ + timeparts: timeparts{ + hour: 13, + prec: 0, + }, + unit: IntervalHour, + }, + }, + { + in: 12.67, + tt: IntervalMicrosecond, + want: &Interval{ + timeparts: timeparts{ + nsec: 13000, + prec: 6, + }, + unit: IntervalMicrosecond, + }, + }, + { + in: 123, + tt: IntervalDay, + want: &Interval{ + timeparts: timeparts{ + day: 123, + prec: 0, + }, + unit: IntervalDay, + }, + }, + } + + for _, tc := range testCases { + res := ParseIntervalFloat(tc.in, tc.tt, false) + assert.Equal(t, tc.want, res) + + res = ParseIntervalDecimal(decimal.NewFromFloat(tc.in), 6, tc.tt, false) + assert.Equal(t, tc.want, res) + } + + // Neg interval case + res := ParseIntervalFloat(123.4, IntervalSecond, true) + want := &Interval{ + timeparts: timeparts{ + sec: -123, + nsec: -400000000, + prec: 6, + }, + unit: IntervalSecond, + } + assert.Equal(t, want, res) +} + +func TestInRange(t *testing.T) { + testCases := []struct { + in Interval + wantInRange bool + }{ + { + in: Interval{ + timeparts: timeparts{ + day: 3652425, + }, + }, + wantInRange: false, + }, + { + in: Interval{ + timeparts: timeparts{ + day: 3652424, + }, + }, + wantInRange: true, + }, + { + in: Interval{ + timeparts: timeparts{ + hour: 3652425 * 24, + }, + }, + wantInRange: false, + }, + { + in: Interval{ + timeparts: timeparts{ + hour: 3652424 * 24, + }, + }, + wantInRange: true, + }, + { + in: Interval{ + timeparts: timeparts{ + min: 3652425 * 24 * 60, + }, + }, + wantInRange: false, + }, + { + in: Interval{ + timeparts: timeparts{ + min: 3652424 * 24 * 60, + }, + }, + wantInRange: true, + }, + { + in: Interval{ + timeparts: timeparts{ + sec: 3652425 * 24 * 60 * 60, + }, + }, + wantInRange: false, + }, + { + in: Interval{ + timeparts: timeparts{ + sec: 3652424 * 24 * 60 * 60, + }, + }, + wantInRange: true, + }, + } + + for _, tc := range testCases { + got := tc.in.inRange() + + if tc.wantInRange { + assert.True(t, got, "Interval %v should be within valid range", tc.in) + } else { + assert.False(t, got, "Interval %v should be out of valid range", tc.in) + } + } +} diff --git a/go/mysql/datetime/mydate_test.go b/go/mysql/datetime/mydate_test.go index ba575ed4e05..bb5073b8ff8 100644 --- a/go/mysql/datetime/mydate_test.go +++ b/go/mysql/datetime/mydate_test.go @@ -55,5 +55,13 @@ func TestDayNumberFields(t *testing.T) { assert.Equal(t, tc[3], int(d)) assert.Equalf(t, tc[0], MysqlDayNumber(tc[1], tc[2], tc[3]), "date %d-%d-%d", tc[1], tc[2], tc[3]) + + wantDate := Date{ + year: uint16(tc[1]), + month: uint8(tc[2]), + day: uint8(tc[3]), + } + got := DateFromDayNumber(tc[0]) + assert.Equal(t, wantDate, got) } } diff --git a/go/mysql/datetime/parse_test.go b/go/mysql/datetime/parse_test.go index 66fb8a73b2f..a219f518995 100644 --- a/go/mysql/datetime/parse_test.go +++ b/go/mysql/datetime/parse_test.go @@ -22,6 +22,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/mysql/decimal" ) func TestParseDate(t *testing.T) { @@ -332,3 +334,255 @@ func TestParseDateTimeInt64(t *testing.T) { }) } } + +func TestParseDateTimeFloat(t *testing.T) { + type datetime struct { + year int + month int + day int + hour int + minute int + second int + nanosecond int + } + tests := []struct { + input float64 + prec int + output datetime + outPrec int + l int + err bool + }{ + {input: 1, prec: 3, outPrec: 3, output: datetime{}, err: true}, + {input: 20221012000000.101562, prec: -2, outPrec: 6, output: datetime{2022, 10, 12, 0, 0, 0, 101562500}}, + {input: 20221012112233.125000, prec: 3, outPrec: 3, output: datetime{2022, 10, 12, 11, 22, 33, 125000000}}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%f", test.input), func(t *testing.T) { + got, p, ok := ParseDateTimeFloat(test.input, test.prec) + if test.err { + if !got.IsZero() { + assert.Equal(t, test.output.year, got.Date.Year()) + assert.Equal(t, test.output.month, got.Date.Month()) + assert.Equal(t, test.output.day, got.Date.Day()) + assert.Equal(t, test.output.hour, got.Time.Hour()) + assert.Equal(t, test.output.minute, got.Time.Minute()) + assert.Equal(t, test.output.second, got.Time.Second()) + assert.Equal(t, test.output.nanosecond, got.Time.Nanosecond()) + } + assert.Falsef(t, ok, "did not fail to parse %s", test.input) + return + } + + require.True(t, ok) + assert.Equal(t, test.outPrec, p) + assert.Equal(t, test.output.year, got.Date.Year()) + assert.Equal(t, test.output.month, got.Date.Month()) + assert.Equal(t, test.output.day, got.Date.Day()) + assert.Equal(t, test.output.hour, got.Time.Hour()) + assert.Equal(t, test.output.minute, got.Time.Minute()) + assert.Equal(t, test.output.second, got.Time.Second()) + assert.Equal(t, test.output.nanosecond, got.Time.Nanosecond()) + }) + } +} + +func TestParseDateTimeDecimal(t *testing.T) { + type datetime struct { + year int + month int + day int + hour int + minute int + second int + nanosecond int + } + tests := []struct { + input decimal.Decimal + prec int + output datetime + outPrec int + l int32 + err bool + }{ + {input: decimal.NewFromFloat(1), l: 6, prec: 3, outPrec: 3, output: datetime{}, err: true}, + {input: decimal.NewFromFloat(20221012000000.101562), l: 6, prec: -2, outPrec: 6, output: datetime{2022, 10, 12, 0, 0, 0, 100000000}}, + {input: decimal.NewFromFloat(20221012112233.125000), l: 6, prec: 3, outPrec: 3, output: datetime{2022, 10, 12, 11, 22, 33, 125000000}}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%v", test.input), func(t *testing.T) { + got, p, ok := ParseDateTimeDecimal(test.input, test.l, test.prec) + if test.err { + if !got.IsZero() { + assert.Equal(t, test.output.year, got.Date.Year()) + assert.Equal(t, test.output.month, got.Date.Month()) + assert.Equal(t, test.output.day, got.Date.Day()) + assert.Equal(t, test.output.hour, got.Time.Hour()) + assert.Equal(t, test.output.minute, got.Time.Minute()) + assert.Equal(t, test.output.second, got.Time.Second()) + assert.Equal(t, test.output.nanosecond, got.Time.Nanosecond()) + } + assert.Falsef(t, ok, "did not fail to parse %s", test.input) + return + } + + require.True(t, ok) + assert.Equal(t, test.outPrec, p) + assert.Equal(t, test.output.year, got.Date.Year()) + assert.Equal(t, test.output.month, got.Date.Month()) + assert.Equal(t, test.output.day, got.Date.Day()) + assert.Equal(t, test.output.hour, got.Time.Hour()) + assert.Equal(t, test.output.minute, got.Time.Minute()) + assert.Equal(t, test.output.second, got.Time.Second()) + assert.Equal(t, test.output.nanosecond, got.Time.Nanosecond()) + }) + } +} + +func TestParseDateFloatAndDecimal(t *testing.T) { + type date struct { + year int + month int + day int + } + tests := []struct { + input float64 + prec int + output date + outPrec int + l int32 + err bool + }{ + {input: 1, output: date{0, 0, 1}, err: true}, + {input: 20221012.102, output: date{2022, 10, 12}}, + {input: 20221212.52, output: date{2022, 12, 12}}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%f", test.input), func(t *testing.T) { + got, ok := ParseDateFloat(test.input) + if test.err { + if !got.IsZero() { + assert.Equal(t, test.output.year, got.Year()) + assert.Equal(t, test.output.month, got.Month()) + assert.Equal(t, test.output.day, got.Day()) + } + assert.Falsef(t, ok, "did not fail to parse %s", test.input) + return + } + + require.True(t, ok) + assert.Equal(t, test.output.year, got.Year()) + assert.Equal(t, test.output.month, got.Month()) + assert.Equal(t, test.output.day, got.Day()) + + got, ok = ParseDateDecimal(decimal.NewFromFloat(test.input)) + if test.err { + if !got.IsZero() { + assert.Equal(t, test.output.year, got.Year()) + assert.Equal(t, test.output.month, got.Month()) + assert.Equal(t, test.output.day, got.Day()) + } + assert.Falsef(t, ok, "did not fail to parse %s", test.input) + return + } + + require.True(t, ok) + assert.Equal(t, test.output.year, got.Year()) + assert.Equal(t, test.output.month, got.Month()) + assert.Equal(t, test.output.day, got.Day()) + }) + } +} + +func TestParseTimeFloat(t *testing.T) { + type time struct { + hour int + minute int + second int + nanosecond int + } + tests := []struct { + input float64 + prec int + outPrec int + output time + err bool + }{ + {input: 1, prec: 1, outPrec: 1, output: time{0, 0, 1, 0}, err: false}, + {input: 201012.102, prec: -1, outPrec: 6, output: time{20, 10, 12, 102000000}}, + {input: 201212.52, prec: -1, outPrec: 6, output: time{20, 12, 12, 519999999}}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%f", test.input), func(t *testing.T) { + got, p, ok := ParseTimeFloat(test.input, test.prec) + if test.err { + if !got.IsZero() { + assert.Equal(t, test.outPrec, p) + assert.Equal(t, test.output.hour, got.Hour()) + assert.Equal(t, test.output.minute, got.Minute()) + assert.Equal(t, test.output.second, got.Second()) + assert.Equal(t, test.output.nanosecond, got.Nanosecond()) + } + assert.Falsef(t, ok, "did not fail to parse %s", test.input) + return + } + + require.True(t, ok) + assert.Equal(t, test.outPrec, p) + assert.Equal(t, test.output.hour, got.Hour()) + assert.Equal(t, test.output.minute, got.Minute()) + assert.Equal(t, test.output.second, got.Second()) + assert.Equal(t, test.output.nanosecond, got.Nanosecond()) + }) + } +} + +func TestParseTimeDecimal(t *testing.T) { + type time struct { + hour int + minute int + second int + nanosecond int + } + tests := []struct { + input decimal.Decimal + l int32 + prec int + outPrec int + output time + err bool + }{ + {input: decimal.NewFromFloat(1), l: 6, prec: 1, outPrec: 1, output: time{0, 0, 1, 0}, err: false}, + {input: decimal.NewFromFloat(201012.102), l: 6, prec: -1, outPrec: 6, output: time{20, 10, 12, 102000000}}, + {input: decimal.NewFromFloat(201212.52), l: 6, prec: -1, outPrec: 6, output: time{20, 12, 12, 520000000}}, + {input: decimal.NewFromFloat(201212.52), l: 10, prec: -1, outPrec: 9, output: time{20, 12, 12, 520000000}}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%v", test.input), func(t *testing.T) { + got, p, ok := ParseTimeDecimal(test.input, test.l, test.prec) + if test.err { + if !got.IsZero() { + assert.Equal(t, test.outPrec, p) + assert.Equal(t, test.output.hour, got.Hour()) + assert.Equal(t, test.output.minute, got.Minute()) + assert.Equal(t, test.output.second, got.Second()) + assert.Equal(t, test.output.nanosecond, got.Nanosecond()) + } + assert.Falsef(t, ok, "did not fail to parse %s", test.input) + return + } + + require.True(t, ok) + assert.Equal(t, test.outPrec, p) + assert.Equal(t, test.output.hour, got.Hour()) + assert.Equal(t, test.output.minute, got.Minute()) + assert.Equal(t, test.output.second, got.Second()) + assert.Equal(t, test.output.nanosecond, got.Nanosecond()) + }) + } +} diff --git a/go/mysql/datetime/strftime_test.go b/go/mysql/datetime/strftime_test.go new file mode 100644 index 00000000000..e2ad4bb9c12 --- /dev/null +++ b/go/mysql/datetime/strftime_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package datetime + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNew(t *testing.T) { + in := "%a-%Y" + res, err := New(in) + assert.NoError(t, err) + assert.Equal(t, res.Pattern(), in) + assert.Equal(t, res.compiled[0].format([]byte{}, DateTime{Date: Date{2024, 3, 15}}, 1), []byte("Fri")) + assert.Equal(t, res.compiled[1].format([]byte{}, DateTime{Date: Date{2024, 3, 15}}, 1), []byte("-")) + assert.Equal(t, res.compiled[2].format([]byte{}, DateTime{Date: Date{2024, 3, 15}}, 1), []byte("2024")) + + in = "%" + res, err = New(in) + assert.Nil(t, res) + assert.Error(t, err) + + in = "-" + res, err = New(in) + assert.NoError(t, err) + assert.Equal(t, res.Pattern(), in) + assert.Equal(t, res.compiled[0].format([]byte{}, DateTime{Date: Date{2024, 3, 15}}, 1), []byte("-")) +} + +func TestStrftimeFormat(t *testing.T) { + testCases := []struct { + in string + want_YYYY_MM_DD string + want_YYYY_M_D string + }{ + {"1999-12-31 23:59:58.999", "1999-12-31", "1999-12-31"}, + {"2000-01-02 03:04:05", "2000-01-02", "2000-1-2"}, + {"2001-01-01 01:04:05", "2001-01-01", "2001-1-1"}, + } + + for _, tc := range testCases { + t.Run(tc.in, func(t *testing.T) { + dt, _, ok := ParseDateTime(tc.in, -1) + require.True(t, ok) + + got := Date_YYYY_MM_DD.Format(dt, 6) + assert.Equal(t, []byte(tc.want_YYYY_MM_DD), got) + + got = Date_YYYY_M_D.Format(dt, 6) + assert.Equal(t, []byte(tc.want_YYYY_M_D), got) + + res := Date_YYYY_MM_DD.FormatString(dt, 6) + assert.Equal(t, tc.want_YYYY_MM_DD, res) + + res = Date_YYYY_M_D.FormatString(dt, 6) + assert.Equal(t, tc.want_YYYY_M_D, res) + + dst := []byte("test: ") + b := Date_YYYY_MM_DD.AppendFormat(dst, dt, 6) + want := append([]byte("test: "), []byte(tc.want_YYYY_MM_DD)...) + assert.Equal(t, want, b) + }) + } +} + +// TODO: Add test for FormatNumeric