diff --git a/expression/integration_test.go b/expression/integration_test.go index 1b98ac818e672..5b0162ced2be5 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -1770,11 +1770,11 @@ func (s *testIntegrationSuite) TestTimeBuiltin(c *C) { {"\"2011-11-11\"", "\"abc1000\"", "MICROSECOND", "", ""}, {"\"20111111 10:10:10\"", "\"1\"", "DAY", "", ""}, - {"\"2011-11-11\"", "\"10\"", "SECOND_MICROSECOND", "", ""}, - {"\"2011-11-11\"", "\"10.0000\"", "MINUTE_MICROSECOND", "", ""}, - {"\"2011-11-11\"", "\"10:10:10\"", "MINUTE_MICROSECOND", "", ""}, + {"\"2011-11-11\"", "\"10\"", "SECOND_MICROSECOND", "2011-11-11 00:00:00.100000", "2011-11-10 23:59:59.900000"}, + {"\"2011-11-11\"", "\"10.0000\"", "MINUTE_MICROSECOND", "2011-11-11 00:00:10", "2011-11-10 23:59:50"}, + {"\"2011-11-11\"", "\"10:10:10\"", "MINUTE_MICROSECOND", "2011-11-11 00:10:10.100000", "2011-11-10 23:49:49.900000"}, - {"cast(\"2011-11-11\" as datetime)", "\"10:10:10\"", "MINUTE_MICROSECOND", "", ""}, + {"cast(\"2011-11-11\" as datetime)", "\"10:10:10\"", "MINUTE_MICROSECOND", "2011-11-11 00:10:10.100000", "2011-11-10 23:49:49.900000"}, {"cast(\"2011-11-11 00:00:00\" as datetime)", "1", "DAY", "2011-11-12 00:00:00", "2011-11-10 00:00:00"}, {"cast(\"2011-11-11 00:00:00\" as datetime)", "10", "HOUR", "2011-11-11 10:00:00", "2011-11-10 14:00:00"}, {"cast(\"2011-11-11 00:00:00\" as datetime)", "10", "MINUTE", "2011-11-11 00:10:00", "2011-11-10 23:50:00"}, @@ -1785,7 +1785,7 @@ func (s *testIntegrationSuite) TestTimeBuiltin(c *C) { {"cast(\"2011-11-11 00:00:00\" as datetime)", "\"10\"", "MINUTE", "2011-11-11 00:10:00", "2011-11-10 23:50:00"}, {"cast(\"2011-11-11 00:00:00\" as datetime)", "\"10\"", "SECOND", "2011-11-11 00:00:10", "2011-11-10 23:59:50"}, - {"cast(\"2011-11-11\" as date)", "\"10:10:10\"", "MINUTE_MICROSECOND", "", ""}, + {"cast(\"2011-11-11\" as date)", "\"10:10:10\"", "MINUTE_MICROSECOND", "2011-11-11 00:10:10.100000", "2011-11-10 23:49:49.900000"}, {"cast(\"2011-11-11 00:00:00\" as date)", "1", "DAY", "2011-11-12", "2011-11-10"}, {"cast(\"2011-11-11 00:00:00\" as date)", "10", "HOUR", "2011-11-11 10:00:00", "2011-11-10 14:00:00"}, {"cast(\"2011-11-11 00:00:00\" as date)", "10", "MINUTE", "2011-11-11 00:10:00", "2011-11-10 23:50:00"}, diff --git a/types/time.go b/types/time.go index 354124d77a992..8b1efd4231cd3 100644 --- a/types/time.go +++ b/types/time.go @@ -77,6 +77,50 @@ const ( TimeMaxValueSeconds = TimeMaxHour*3600 + TimeMaxMinute*60 + TimeMaxSecond ) +const ( + // YearIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format + YearIndex = 0 + iota + // MonthIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format + MonthIndex + // DayIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format + DayIndex + // HourIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format + HourIndex + // MinuteIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format + MinuteIndex + // SecondIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format + SecondIndex + // MicrosecondIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format + MicrosecondIndex +) + +const ( + // YearMonthMaxCnt is max parameters count 'YEARS-MONTHS' expr Format allowed + YearMonthMaxCnt = 2 + // DayHourMaxCnt is max parameters count 'DAYS HOURS' expr Format allowed + DayHourMaxCnt = 2 + // DayMinuteMaxCnt is max parameters count 'DAYS HOURS:MINUTES' expr Format allowed + DayMinuteMaxCnt = 3 + // DaySecondMaxCnt is max parameters count 'DAYS HOURS:MINUTES:SECONDS' expr Format allowed + DaySecondMaxCnt = 4 + // DayMicrosecondMaxCnt is max parameters count 'DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format allowed + DayMicrosecondMaxCnt = 5 + // HourMinuteMaxCnt is max parameters count 'HOURS:MINUTES' expr Format allowed + HourMinuteMaxCnt = 2 + // HourSecondMaxCnt is max parameters count 'HOURS:MINUTES:SECONDS' expr Format allowed + HourSecondMaxCnt = 3 + // HourMicrosecondMaxCnt is max parameters count 'HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format allowed + HourMicrosecondMaxCnt = 4 + // MinuteSecondMaxCnt is max parameters count 'MINUTES:SECONDS' expr Format allowed + MinuteSecondMaxCnt = 2 + // MinuteMicrosecondMaxCnt is max parameters count 'MINUTES:SECONDS.MICROSECONDS' expr Format allowed + MinuteMicrosecondMaxCnt = 3 + // SecondMicrosecondMaxCnt is max parameters count 'SECONDS.MICROSECONDS' expr Format allowed + SecondMicrosecondMaxCnt = 2 + // TimeValueCnt is parameters count 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format + TimeValueCnt = 7 +) + // Zero values for different types. var ( // ZeroDuration is the zero value for Duration type. @@ -1555,234 +1599,67 @@ func extractSingleTimeValue(unit string, format string) (int64, int64, int64, fl return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit) } -// extractSecondMicrosecond extracts second and microsecond from a string and its format is `SS.FFFFFF`. -func extractSecondMicrosecond(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, ".") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - seconds, err := strconv.ParseFloat(fields[0], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - microseconds, err := strconv.ParseFloat(alignFrac(fields[1], MaxFsp), 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - return 0, 0, 0, seconds*float64(gotime.Second) + microseconds*float64(gotime.Microsecond), nil -} - -// extractMinuteMicrosecond extracts minutes and microsecond from a string and its format is `MM:SS.FFFFFF`. -func extractMinuteMicrosecond(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, ":") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - minutes, err := strconv.ParseFloat(fields[0], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - _, _, _, value, err := extractSecondMicrosecond(fields[1]) - if err != nil { - return 0, 0, 0, 0, errors.Trace(err) - } - - return 0, 0, 0, minutes*float64(gotime.Minute) + value, nil -} - -// extractMinuteSecond extracts minutes and second from a string and its format is `MM:SS`. -func extractMinuteSecond(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, ":") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - minutes, err := strconv.ParseFloat(fields[0], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - seconds, err := strconv.ParseFloat(fields[1], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - return 0, 0, 0, minutes*float64(gotime.Minute) + seconds*float64(gotime.Second), nil -} - -// extractHourMicrosecond extracts hour and microsecond from a string and its format is `HH:MM:SS.FFFFFF`. -func extractHourMicrosecond(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, ":") - if len(fields) != 3 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - hours, err := strconv.ParseFloat(fields[0], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - minutes, err := strconv.ParseFloat(fields[1], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - _, _, _, value, err := extractSecondMicrosecond(fields[2]) - if err != nil { - return 0, 0, 0, 0, errors.Trace(err) - } - - return 0, 0, 0, hours*float64(gotime.Hour) + minutes*float64(gotime.Minute) + value, nil -} - -// extractHourSecond extracts hour and second from a string and its format is `HH:MM:SS`. -func extractHourSecond(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, ":") - if len(fields) != 3 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - hours, err := strconv.ParseFloat(fields[0], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - minutes, err := strconv.ParseFloat(fields[1], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - seconds, err := strconv.ParseFloat(fields[2], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - return 0, 0, 0, hours*float64(gotime.Hour) + minutes*float64(gotime.Minute) + seconds*float64(gotime.Second), nil -} - -// extractHourMinute extracts hour and minute from a string and its format is `HH:MM`. -func extractHourMinute(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, ":") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - hours, err := strconv.ParseFloat(fields[0], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - minutes, err := strconv.ParseFloat(fields[1], 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - return 0, 0, 0, hours*float64(gotime.Hour) + minutes*float64(gotime.Minute), nil -} - -// extractDayMicrosecond extracts day and microsecond from a string and its format is `DD HH:MM:SS.FFFFFF`. -func extractDayMicrosecond(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, " ") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - days, err := strconv.ParseInt(fields[0], 10, 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) +// extractTimeValue extracts years, months, days, microseconds from a string +// MySQL permits any punctuation delimiter in the expr format. +// See https://dev.mysql.com/doc/refman/8.0/en/expressions.html#temporal-intervals +func extractTimeValue(format string, index, cnt int) (int64, int64, int64, float64, error) { + neg := false + originalFmt := format + format = strings.TrimSpace(format) + if len(format) > 0 && format[0] == '-' { + neg = true + format = format[1:] } - - _, _, _, value, err := extractHourMicrosecond(fields[1]) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + fields := make([]string, TimeValueCnt) + for i := range fields { + fields[i] = "0" } - - return 0, 0, days, value, nil -} - -// extractDaySecond extracts day and hour from a string and its format is `DD HH:MM:SS`. -func extractDaySecond(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, " ") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + matches := numericRegex.FindAllString(format, -1) + if len(matches) > cnt { + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - - days, err := strconv.ParseInt(fields[0], 10, 64) - if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + for i := range matches { + if neg { + fields[index] = "-" + matches[len(matches)-1-i] + } else { + fields[index] = matches[len(matches)-1-i] + } + index-- } - _, _, _, value, err := extractHourSecond(fields[1]) + years, err := strconv.ParseInt(fields[YearIndex], 10, 64) if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - - return 0, 0, days, value, nil -} - -// extractDayMinute extracts day and minute from a string and its format is `DD HH:MM`. -func extractDayMinute(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, " ") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - days, err := strconv.ParseInt(fields[0], 10, 64) + months, err := strconv.ParseInt(fields[MonthIndex], 10, 64) if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - - _, _, _, value, err := extractHourMinute(fields[1]) + days, err := strconv.ParseInt(fields[DayIndex], 10, 64) if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - return 0, 0, days, value, nil -} - -// extractDayHour extracts day and hour from a string and its format is `DD HH`. -func extractDayHour(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, " ") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - days, err := strconv.ParseInt(fields[0], 10, 64) + hours, err := strconv.ParseFloat(fields[HourIndex], 64) if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - - hours, err := strconv.ParseFloat(fields[1], 64) + minutes, err := strconv.ParseFloat(fields[MinuteIndex], 64) if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - - return 0, 0, days, hours * float64(gotime.Hour), nil -} - -// extractYearMonth extracts year and month from a string and its format is `YYYY-MM`. -func extractYearMonth(format string) (int64, int64, int64, float64, error) { - fields := strings.Split(format, "-") - if len(fields) != 2 { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) - } - - years, err := strconv.ParseInt(fields[0], 10, 64) + seconds, err := strconv.ParseFloat(fields[SecondIndex], 64) if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - - months, err := strconv.ParseInt(fields[1], 10, 64) + microseconds, err := strconv.ParseFloat(alignFrac(fields[MicrosecondIndex], MaxFsp), 64) if err != nil { - return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } + durations := hours*float64(gotime.Hour) + minutes*float64(gotime.Minute) + + seconds*float64(gotime.Second) + microseconds*float64(gotime.Microsecond) - return years, months, 0, 0, nil + return years, months, days, durations, nil } // ExtractTimeValue extracts time value from time unit and format. @@ -1791,27 +1668,27 @@ func ExtractTimeValue(unit string, format string) (int64, int64, int64, float64, case "MICROSECOND", "SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "QUARTER", "YEAR": return extractSingleTimeValue(unit, format) case "SECOND_MICROSECOND": - return extractSecondMicrosecond(format) + return extractTimeValue(format, MicrosecondIndex, SecondMicrosecondMaxCnt) case "MINUTE_MICROSECOND": - return extractMinuteMicrosecond(format) + return extractTimeValue(format, MicrosecondIndex, MinuteMicrosecondMaxCnt) case "MINUTE_SECOND": - return extractMinuteSecond(format) + return extractTimeValue(format, SecondIndex, MinuteSecondMaxCnt) case "HOUR_MICROSECOND": - return extractHourMicrosecond(format) + return extractTimeValue(format, MicrosecondIndex, HourMicrosecondMaxCnt) case "HOUR_SECOND": - return extractHourSecond(format) + return extractTimeValue(format, SecondIndex, HourSecondMaxCnt) case "HOUR_MINUTE": - return extractHourMinute(format) + return extractTimeValue(format, MinuteIndex, HourMinuteMaxCnt) case "DAY_MICROSECOND": - return extractDayMicrosecond(format) + return extractTimeValue(format, MicrosecondIndex, DayMicrosecondMaxCnt) case "DAY_SECOND": - return extractDaySecond(format) + return extractTimeValue(format, SecondIndex, DaySecondMaxCnt) case "DAY_MINUTE": - return extractDayMinute(format) + return extractTimeValue(format, MinuteIndex, DayMinuteMaxCnt) case "DAY_HOUR": - return extractDayHour(format) + return extractTimeValue(format, HourIndex, DayHourMaxCnt) case "YEAR_MONTH": - return extractYearMonth(format) + return extractTimeValue(format, MonthIndex, YearMonthMaxCnt) default: return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit) } @@ -2378,6 +2255,9 @@ var twoDigitRegex = regexp.MustCompile("^[1-9][0-9]?") // oneToSixDigitRegex: it was just for [0, 999999] var oneToSixDigitRegex = regexp.MustCompile("^[0-9]{0,6}") +// numericRegex: it was for any numeric characters +var numericRegex = regexp.MustCompile("[0-9]+") + // parseTwoNumeric is used for pattens 0..31 0..24 0..60 and so on. // It returns the parsed int, and remain data after parse. func parseTwoNumeric(input string) (int, string) { diff --git a/types/time_test.go b/types/time_test.go index 20335b8d4efff..65175757740f6 100644 --- a/types/time_test.go +++ b/types/time_test.go @@ -1068,3 +1068,242 @@ func (s *testTimeSuite) TestCheckTimestamp(c *C) { } } } + +func (s *testTimeSuite) TestCurrentTime(c *C) { + res := types.CurrentTime(mysql.TypeTimestamp) + c.Assert(res.Time, NotNil) + c.Assert(res.Type, Equals, mysql.TypeTimestamp) + c.Assert(res.Fsp, Equals, 0) +} + +func (s *testTimeSuite) TestInvalidZero(c *C) { + in := types.Time{ + Time: types.ZeroTime, + Type: mysql.TypeTimestamp, + Fsp: types.DefaultFsp, + } + c.Assert(in.InvalidZero(), Equals, true) + in.Time = types.FromDate(2019, 00, 00, 00, 00, 00, 00) + c.Assert(in.InvalidZero(), Equals, true) + in.Time = types.FromDate(2019, 04, 12, 12, 00, 00, 00) + c.Assert(in.InvalidZero(), Equals, false) +} + +func (s *testTimeSuite) TestGetFsp(c *C) { + res := types.GetFsp("2019:04:12 14:00:00.123456") + c.Assert(res, Equals, 6) + + res = types.GetFsp("2019:04:12 14:00:00.1234567890") + c.Assert(res, Equals, 6) + + res = types.GetFsp("2019:04:12 14:00:00.1") + c.Assert(res, Equals, 1) + + res = types.GetFsp("2019:04:12 14:00:00") + c.Assert(res, Equals, 0) +} + +func (s *testTimeSuite) TestExtractDatetimeNum(c *C) { + in := types.Time{ + Time: types.FromDate(2019, 04, 12, 14, 00, 00, 0000), + Type: mysql.TypeTimestamp, + Fsp: types.DefaultFsp, + } + + res, err := types.ExtractDatetimeNum(&in, "day") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(12)) + + res, err = types.ExtractDatetimeNum(&in, "week") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(14)) + + res, err = types.ExtractDatetimeNum(&in, "MONTH") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(4)) + + res, err = types.ExtractDatetimeNum(&in, "QUARTER") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(2)) + + res, err = types.ExtractDatetimeNum(&in, "YEAR") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(2019)) + + res, err = types.ExtractDatetimeNum(&in, "DAY_MICROSECOND") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(12140000000000)) + + res, err = types.ExtractDatetimeNum(&in, "DAY_SECOND") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(12140000)) + + res, err = types.ExtractDatetimeNum(&in, "DAY_MINUTE") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(121400)) + + res, err = types.ExtractDatetimeNum(&in, "DAY_HOUR") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(1214)) + + res, err = types.ExtractDatetimeNum(&in, "YEAR_MONTH") + c.Assert(err, IsNil) + c.Assert(res, Equals, int64(201904)) + + res, err = types.ExtractDatetimeNum(&in, "TEST_ERROR") + c.Assert(res, Equals, int64(0)) + c.Assert(err, ErrorMatches, "invalid unit.*") +} + +func (s *testTimeSuite) TestExtractDurationNum(c *C) { + in := types.Duration{Duration: time.Duration(3600 * 24 * 365), Fsp: types.DefaultFsp} + tbl := []struct { + unit string + expect int64 + }{ + {"MICROSECOND", 31536}, + {"SECOND", 0}, + {"MINUTE", 0}, + {"HOUR", 0}, + {"SECOND_MICROSECOND", 31536}, + {"MINUTE_MICROSECOND", 31536}, + {"MINUTE_SECOND", 0}, + {"HOUR_MICROSECOND", 31536}, + {"HOUR_SECOND", 0}, + {"HOUR_MINUTE", 0}, + } + + for _, col := range tbl { + res, err := types.ExtractDurationNum(&in, col.unit) + c.Assert(err, IsNil) + c.Assert(res, Equals, col.expect) + } + res, err := types.ExtractDurationNum(&in, "TEST_ERROR") + c.Assert(res, Equals, int64(0)) + c.Assert(err, ErrorMatches, "invalid unit.*") +} + +func (s *testTimeSuite) TestExtractTimeValue(c *C) { + tbl := []struct { + format string + unit string + res1 int64 + res2 int64 + res3 int64 + res4 float64 + }{ + {"52", "WEEK", 0, 0, 52 * 7, 0}, + {"12", "DAY", 0, 0, 12, 0}, + {"04", "MONTH", 0, 04, 0, 0}, + {"1", "QUARTER", 0, 1 * 3, 0, 0}, + {"2019", "YEAR", 2019, 0, 0, 0}, + {"10567890", "SECOND_MICROSECOND", 0, 0, 0, 1.056789e+10}, + {"10.567890", "SECOND_MICROSECOND", 0, 0, 0, 1.056789e+10}, + {"-10.567890", "SECOND_MICROSECOND", 0, 0, 0, -1.056789e+10}, + {"35:10567890", "MINUTE_SECOND", 0, 0, 0, 1.056999e+16}, + {"3510567890", "MINUTE_SECOND", 0, 0, 0, 3.51056789e+18}, + {"11:35:10.567890", "HOUR_MICROSECOND", 0, 0, 0, 4.171056789e+13}, + {"567890", "HOUR_MICROSECOND", 0, 0, 0, 5.6789e+08}, + {"14:00", "HOUR_MINUTE", 0, 0, 0, 5.04e+13}, + {"14", "HOUR_MINUTE", 0, 0, 0, 8.4e+11}, + {"12 14:00:00.345", "DAY_MICROSECOND", 0, 0, 12, 5.0400345e+13}, + {"12 14:00:00", "DAY_SECOND", 0, 0, 12, 5.04e+13}, + {"12 14:00", "DAY_MINUTE", 0, 0, 12, 5.04e+13}, + {"12 14", "DAY_HOUR", 0, 0, 12, 5.04e+13}, + {"1:1", "DAY_HOUR", 0, 0, 1, 3.6e+12}, + {"aa1bb1", "DAY_HOUR", 0, 0, 1, 3.6e+12}, + {"-1:1", "DAY_HOUR", 0, 0, -1, -3.6e+12}, + {"-aa1bb1", "DAY_HOUR", 0, 0, -1, -3.6e+12}, + {"2019-12", "YEAR_MONTH", 2019, 12, 0, 0}, + {"1 1", "YEAR_MONTH", 1, 1, 0, 0}, + {"aa1bb1", "YEAR_MONTH", 1, 1, 0, 0}, + {"-1 1", "YEAR_MONTH", -1, -1, 0, 0}, + {"-aa1bb1", "YEAR_MONTH", -1, -1, 0, 0}, + {" \t\n\r\n - aa1bb1 \t\n ", "YEAR_MONTH", -1, -1, 0, 0}, + } + for _, col := range tbl { + res1, res2, res3, res4, err := types.ExtractTimeValue(col.unit, col.format) + c.Assert(res1, Equals, col.res1) + c.Assert(res2, Equals, col.res2) + c.Assert(res3, Equals, col.res3) + c.Assert(res4, Equals, col.res4) + c.Assert(err, IsNil) + } + +} + +func (s *testTimeSuite) TestIsClockUnit(c *C) { + tbl := []struct { + input string + expected bool + }{ + {"MICROSECOND", true}, + {"SECOND", true}, + {"MINUTE", true}, + {"HOUR", true}, + {"SECOND_MICROSECOND", true}, + {"MINUTE_MICROSECOND", true}, + {"MINUTE_SECOND", true}, + {"HOUR_MICROSECOND", true}, + {"HOUR_SECOND", true}, + {"HOUR_MINUTE", true}, + {"DAY_MICROSECOND", true}, + {"DAY_SECOND", true}, + {"DAY_MINUTE", true}, + {"DAY_HOUR", true}, + {"TEST", false}, + } + for _, col := range tbl { + output := types.IsClockUnit(col.input) + c.Assert(output, Equals, col.expected) + } +} + +func (s *testTimeSuite) TestIsDateFormat(c *C) { + input := "1234:321" + output := types.IsDateFormat(input) + c.Assert(output, Equals, false) + + input = "2019-04-01" + output = types.IsDateFormat(input) + c.Assert(output, Equals, true) + + input = "2019-4-1" + output = types.IsDateFormat(input) + c.Assert(output, Equals, true) +} + +func (s *testTimeSuite) TestParseTimeFromInt64(c *C) { + sc := mock.NewContext().GetSessionVars().StmtCtx + sc.IgnoreZeroInDate = true + + input := int64(20190412140000) + output, err := types.ParseTimeFromInt64(sc, input) + c.Assert(err, IsNil) + c.Assert(output.Fsp, Equals, types.DefaultFsp) + c.Assert(output.Type, Equals, mysql.TypeDatetime) + c.Assert(output.Time.Year(), Equals, 2019) + c.Assert(output.Time.Month(), Equals, 04) + c.Assert(output.Time.Day(), Equals, 12) + c.Assert(output.Time.Hour(), Equals, 14) + c.Assert(output.Time.Minute(), Equals, 00) + c.Assert(output.Time.Second(), Equals, 00) + c.Assert(output.Time.Microsecond(), Equals, 00) +} + +func (s *testTimeSuite) TestGetFormatType(c *C) { + input := "TEST" + isDuration, isDate := types.GetFormatType(input) + c.Assert(isDuration, Equals, false) + c.Assert(isDate, Equals, false) + + input = "%y %m %d 2019 04 01" + isDuration, isDate = types.GetFormatType(input) + c.Assert(isDuration, Equals, false) + c.Assert(isDate, Equals, true) + + input = "%h 30" + isDuration, isDate = types.GetFormatType(input) + c.Assert(isDuration, Equals, true) + c.Assert(isDate, Equals, false) +}