diff --git a/expression/builtin_time.go b/expression/builtin_time.go index dc586cffc19fe..3b5939469eebf 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -2605,14 +2605,14 @@ func (du *baseDateArithmitical) getIntervalFromDecimal(ctx sessionctx.Context, a interval = "00:" + interval case "SECOND_MICROSECOND": /* keep interval as original decimal */ - case "SECOND", "MICROSECOND": - args[1] = WrapWithCastAsReal(ctx, args[1]) + case "SECOND": + // Decimal's EvalString is like %f format. interval, isNull, err = args[1].EvalString(ctx, row) if isNull || err != nil { return "", true, errors.Trace(err) } default: - // YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE + // YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, MICROSECOND args[1] = WrapWithCastAsInt(ctx, args[1]) interval, isNull, err = args[1].EvalString(ctx, row) if isNull || err != nil { @@ -2632,18 +2632,17 @@ func (du *baseDateArithmitical) getIntervalFromInt(ctx sessionctx.Context, args } func (du *baseDateArithmitical) add(ctx sessionctx.Context, date types.Time, interval string, unit string) (types.Time, bool, error) { - year, month, day, dur, err := types.ExtractTimeValue(unit, interval) - if err != nil { - return types.Time{}, true, errors.Trace(handleInvalidTimeError(ctx, err)) + year, month, day, nano, err := types.ParseDurationValue(unit, interval) + if err := handleInvalidTimeError(ctx, err); err != nil { + return types.Time{}, true, err } goTime, err := date.Time.GoTime(time.Local) - if err != nil { - return types.Time{}, true, errors.Trace(err) + if err := handleInvalidTimeError(ctx, err); err != nil { + return types.Time{}, true, err } - duration := time.Duration(dur) - goTime = goTime.Add(duration) + goTime = goTime.Add(time.Duration(nano)) goTime = types.AddDate(year, month, day, goTime) if goTime.Nanosecond() == 0 { @@ -2654,7 +2653,7 @@ func (du *baseDateArithmitical) add(ctx sessionctx.Context, date types.Time, int date.Time = types.FromGoTime(goTime) overflow, err := types.DateTimeIsOverflow(ctx.GetSessionVars().StmtCtx, date) - if err != nil { + if err := handleInvalidTimeError(ctx, err); err != nil { return types.Time{}, true, err } if overflow { @@ -2664,18 +2663,18 @@ func (du *baseDateArithmitical) add(ctx sessionctx.Context, date types.Time, int } func (du *baseDateArithmitical) sub(ctx sessionctx.Context, date types.Time, interval string, unit string) (types.Time, bool, error) { - year, month, day, dur, err := types.ExtractTimeValue(unit, interval) - if err != nil { - return types.Time{}, true, errors.Trace(handleInvalidTimeError(ctx, err)) + year, month, day, nano, err := types.ParseDurationValue(unit, interval) + if err := handleInvalidTimeError(ctx, err); err != nil { + return types.Time{}, true, err } - year, month, day, dur = -year, -month, -day, -dur + year, month, day, nano = -year, -month, -day, -nano goTime, err := date.Time.GoTime(time.Local) - if err != nil { - return types.Time{}, true, errors.Trace(err) + if err := handleInvalidTimeError(ctx, err); err != nil { + return types.Time{}, true, err } - duration := time.Duration(dur) + duration := time.Duration(nano) goTime = goTime.Add(duration) goTime = types.AddDate(year, month, day, goTime) @@ -2687,7 +2686,7 @@ func (du *baseDateArithmitical) sub(ctx sessionctx.Context, date types.Time, int date.Time = types.FromGoTime(goTime) overflow, err := types.DateTimeIsOverflow(ctx.GetSessionVars().StmtCtx, date) - if err != nil { + if err := handleInvalidTimeError(ctx, err); err != nil { return types.Time{}, true, err } if overflow { diff --git a/expression/integration_test.go b/expression/integration_test.go index 8461321ccc57b..276a9597ada07 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -1768,7 +1768,7 @@ func (s *testIntegrationSuite) TestTimeBuiltin(c *C) { {"\"2011-11-11 00:00:00\"", "10", "MINUTE", "2011-11-11 00:10:00", "2011-11-10 23:50:00"}, {"\"2011-11-11 00:00:00\"", "10", "SECOND", "2011-11-11 00:00:10", "2011-11-10 23:59:50"}, - {"\"2011-11-11\"", "\"abc1000\"", "MICROSECOND", "", ""}, + {"\"2011-11-11\"", "\"abc1000\"", "MICROSECOND", "2011-11-11 00:00:00", "2011-11-11 00:00:00"}, {"\"20111111 10:10:10\"", "\"1\"", "DAY", "", ""}, {"\"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"}, @@ -3895,6 +3895,55 @@ where tk.MustQuery(q) } +func (s *testIntegrationSuite) TestIssue9727(c *C) { + tk := testkit.NewTestKit(c, s.store) + defer s.cleanEnv(c) + + cases := []struct { + sql string + result string + }{ + {`SELECT "1900-01-01 00:00:00" + INTERVAL "100000000:214748364700" MINUTE_SECOND;`, "8895-03-27 22:11:40"}, + {`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 37 SECOND;`, "6255-04-08 15:04:32"}, + {`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 31 MINUTE;`, "5983-01-24 02:08:00"}, + {`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 38 SECOND;`, ""}, + {`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 33 MINUTE;`, ""}, + {`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 30 HOUR;`, ""}, + {`SELECT "1900-01-01 00:00:00" + INTERVAL "1000000000:214748364700" MINUTE_SECOND;`, ""}, + {`SELECT 19000101000000 + INTERVAL "100000000:214748364700" MINUTE_SECOND;`, "8895-03-27 22:11:40"}, + {`SELECT 19000101000000 + INTERVAL 1 << 37 SECOND;`, "6255-04-08 15:04:32"}, + {`SELECT 19000101000000 + INTERVAL 1 << 31 MINUTE;`, "5983-01-24 02:08:00"}, + + {`SELECT "8895-03-27 22:11:40" - INTERVAL "100000000:214748364700" MINUTE_SECOND;`, "1900-01-01 00:00:00"}, + {`SELECT "6255-04-08 15:04:32" - INTERVAL 1 << 37 SECOND;`, "1900-01-01 00:00:00"}, + {`SELECT "5983-01-24 02:08:00" - INTERVAL 1 << 31 MINUTE;`, "1900-01-01 00:00:00"}, + {`SELECT "9999-01-01 00:00:00" - INTERVAL 1 << 39 SECOND;`, ""}, + {`SELECT "9999-01-01 00:00:00" - INTERVAL 1 << 33 MINUTE;`, ""}, + {`SELECT "9999-01-01 00:00:00" - INTERVAL 1 << 30 HOUR;`, ""}, + {`SELECT "9999-01-01 00:00:00" - INTERVAL "10000000000:214748364700" MINUTE_SECOND;`, ""}, + {`SELECT 88950327221140 - INTERVAL "100000000:214748364700" MINUTE_SECOND ;`, "1900-01-01 00:00:00"}, + {`SELECT 62550408150432 - INTERVAL 1 << 37 SECOND;`, "1900-01-01 00:00:00"}, + {`SELECT 59830124020800 - INTERVAL 1 << 31 MINUTE;`, "1900-01-01 00:00:00"}, + + {`SELECT 10000101000000 + INTERVAL "111111111111111111" MICROSECOND;`, `4520-12-21 05:31:51.111111`}, + {`SELECT 10000101000000 + INTERVAL "111111111111.111111" SECOND;`, `4520-12-21 05:31:51.111111`}, + {`SELECT 10000101000000 + INTERVAL "111111111111.111111111" SECOND;`, `4520-12-21 05:31:51.111111`}, + {`SELECT 10000101000000 + INTERVAL "111111111111.111" SECOND;`, `4520-12-21 05:31:51.111000`}, + {`SELECT 10000101000000 + INTERVAL "111111111111." SECOND;`, `4520-12-21 05:31:51`}, + {`SELECT 10000101000000 + INTERVAL "111111111111111111.5" MICROSECOND;`, `4520-12-21 05:31:51.111112`}, + {`SELECT 10000101000000 + INTERVAL "111111111111111112.5" MICROSECOND;`, `4520-12-21 05:31:51.111113`}, + {`SELECT 10000101000000 + INTERVAL "111111111111111111.500000" MICROSECOND;`, `4520-12-21 05:31:51.111112`}, + {`SELECT 10000101000000 + INTERVAL "111111111111111111.50000000" MICROSECOND;`, `4520-12-21 05:31:51.111112`}, + {`SELECT 10000101000000 + INTERVAL "111111111111111111.6" MICROSECOND;`, `4520-12-21 05:31:51.111112`}, + {`SELECT 10000101000000 + INTERVAL "111111111111111111.499999" MICROSECOND;`, `4520-12-21 05:31:51.111111`}, + {`SELECT 10000101000000 + INTERVAL "111111111111111111.499999999999" MICROSECOND;`, `4520-12-21 05:31:51.111111`}, + } + + for _, c := range cases { + tk.MustQuery(c.sql).Check(testkit.Rows(c.result)) + } +} + func (s *testIntegrationSuite) TestTimestampDatumEncode(c *C) { tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") diff --git a/types/time.go b/types/time.go index f0e0ab615e0e4..5f6c9bff0f0c2 100644 --- a/types/time.go +++ b/types/time.go @@ -1602,41 +1602,81 @@ func ExtractDurationNum(d *Duration, unit string) (int64, error) { } } -func extractSingleTimeValue(unit string, format string) (int64, int64, int64, float64, error) { - fv, err := strconv.ParseFloat(format, 64) +func parseSingleTimeValue(unit string, format string) (int64, int64, int64, int64, error) { + // Format is a preformatted number, it format should be A[.[B]]. + decimalPointPos := strings.IndexRune(format, '.') + if decimalPointPos == -1 { + decimalPointPos = len(format) + } + sign := int64(1) + if len(format) > 0 && format[0] == '-' { + sign = int64(-1) + } + iv, err := strconv.ParseInt(format[0:decimalPointPos], 10, 64) if err != nil { return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) } - iv := int64(math.Round(fv)) + riv := iv // Rounded integer value + dv := int64(0) + lf := len(format) - 1 + // Has fraction part + if decimalPointPos < lf { + if lf-decimalPointPos >= 6 { + // MySQL rounds down to 1e-6. + if dv, err = strconv.ParseInt(format[decimalPointPos+1:decimalPointPos+7], 10, 64); err != nil { + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + } + } else { + if dv, err = strconv.ParseInt(format[decimalPointPos+1:]+"000000"[:6-(lf-decimalPointPos)], 10, 64); err != nil { + return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format) + } + } + if dv >= 500000 { // Round up, and we should keep 6 digits for microsecond, so dv should in [000000, 999999]. + riv += sign + } + if unit != "SECOND" { + err = ErrTruncatedWrongValue.GenWithStackByArgs(format) + } + } + const gotimeDay = 24 * gotime.Hour switch strings.ToUpper(unit) { case "MICROSECOND": - return 0, 0, 0, fv * float64(gotime.Microsecond), nil + dayCount := riv / int64(gotimeDay/gotime.Microsecond) + riv %= int64(gotimeDay / gotime.Microsecond) + return 0, 0, dayCount, riv * int64(gotime.Microsecond), err case "SECOND": - return 0, 0, 0, fv * float64(gotime.Second), nil + dayCount := iv / int64(gotimeDay/gotime.Second) + iv %= int64(gotimeDay / gotime.Second) + return 0, 0, dayCount, iv*int64(gotime.Second) + dv*int64(gotime.Microsecond), err case "MINUTE": - return 0, 0, 0, float64(iv * int64(gotime.Minute)), nil + dayCount := riv / int64(gotimeDay/gotime.Minute) + riv %= int64(gotimeDay / gotime.Minute) + return 0, 0, dayCount, riv * int64(gotime.Minute), err case "HOUR": - return 0, 0, 0, float64(iv * int64(gotime.Hour)), nil + dayCount := riv / 24 + riv %= 24 + return 0, 0, dayCount, riv * int64(gotime.Hour), err case "DAY": - return 0, 0, iv, 0, nil + return 0, 0, riv, 0, err case "WEEK": - return 0, 0, 7 * iv, 0, nil + return 0, 0, 7 * riv, 0, err case "MONTH": - return 0, iv, 0, 0, nil + return 0, riv, 0, 0, err case "QUARTER": - return 0, 3 * iv, 0, 0, nil + return 0, 3 * riv, 0, 0, err case "YEAR": - return iv, 0, 0, 0, nil + return riv, 0, 0, 0, err } return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit) } -// extractTimeValue extracts years, months, days, microseconds from a string +// parseTimeValue gets years, months, days, nanoseconds from a string +// nanosecond will not exceed length of single day // 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) { +func parseTimeValue(format string, index, cnt int) (int64, int64, int64, int64, error) { neg := false originalFmt := format format = strings.TrimSpace(format) @@ -1674,55 +1714,57 @@ func extractTimeValue(format string, index, cnt int) (int64, int64, int64, float return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - hours, err := strconv.ParseFloat(fields[HourIndex], 64) + hours, err := strconv.ParseInt(fields[HourIndex], 10, 64) if err != nil { return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - minutes, err := strconv.ParseFloat(fields[MinuteIndex], 64) + minutes, err := strconv.ParseInt(fields[MinuteIndex], 10, 64) if err != nil { return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - seconds, err := strconv.ParseFloat(fields[SecondIndex], 64) + seconds, err := strconv.ParseInt(fields[SecondIndex], 10, 64) if err != nil { return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt) } - microseconds, err := strconv.ParseFloat(alignFrac(fields[MicrosecondIndex], MaxFsp), 64) + microseconds, err := strconv.ParseInt(alignFrac(fields[MicrosecondIndex], MaxFsp), 10, 64) if err != nil { 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, days, durations, nil + seconds = hours*3600 + minutes*60 + seconds + days += seconds / (3600 * 24) + seconds %= 3600 * 24 + return years, months, days, seconds*int64(gotime.Second) + microseconds*int64(gotime.Microsecond), nil } -// ExtractTimeValue extracts time value from time unit and format. -func ExtractTimeValue(unit string, format string) (int64, int64, int64, float64, error) { +// ParseDurationValue parses time value from time unit and format. +// Returns y years m months d days + n nanoseconds +// Nanoseconds will no longer than one day. +func ParseDurationValue(unit string, format string) (y int64, m int64, d int64, n int64, _ error) { switch strings.ToUpper(unit) { case "MICROSECOND", "SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "QUARTER", "YEAR": - return extractSingleTimeValue(unit, format) + return parseSingleTimeValue(unit, format) case "SECOND_MICROSECOND": - return extractTimeValue(format, MicrosecondIndex, SecondMicrosecondMaxCnt) + return parseTimeValue(format, MicrosecondIndex, SecondMicrosecondMaxCnt) case "MINUTE_MICROSECOND": - return extractTimeValue(format, MicrosecondIndex, MinuteMicrosecondMaxCnt) + return parseTimeValue(format, MicrosecondIndex, MinuteMicrosecondMaxCnt) case "MINUTE_SECOND": - return extractTimeValue(format, SecondIndex, MinuteSecondMaxCnt) + return parseTimeValue(format, SecondIndex, MinuteSecondMaxCnt) case "HOUR_MICROSECOND": - return extractTimeValue(format, MicrosecondIndex, HourMicrosecondMaxCnt) + return parseTimeValue(format, MicrosecondIndex, HourMicrosecondMaxCnt) case "HOUR_SECOND": - return extractTimeValue(format, SecondIndex, HourSecondMaxCnt) + return parseTimeValue(format, SecondIndex, HourSecondMaxCnt) case "HOUR_MINUTE": - return extractTimeValue(format, MinuteIndex, HourMinuteMaxCnt) + return parseTimeValue(format, MinuteIndex, HourMinuteMaxCnt) case "DAY_MICROSECOND": - return extractTimeValue(format, MicrosecondIndex, DayMicrosecondMaxCnt) + return parseTimeValue(format, MicrosecondIndex, DayMicrosecondMaxCnt) case "DAY_SECOND": - return extractTimeValue(format, SecondIndex, DaySecondMaxCnt) + return parseTimeValue(format, SecondIndex, DaySecondMaxCnt) case "DAY_MINUTE": - return extractTimeValue(format, MinuteIndex, DayMinuteMaxCnt) + return parseTimeValue(format, MinuteIndex, DayMinuteMaxCnt) case "DAY_HOUR": - return extractTimeValue(format, HourIndex, DayHourMaxCnt) + return parseTimeValue(format, HourIndex, DayHourMaxCnt) case "YEAR_MONTH": - return extractTimeValue(format, MonthIndex, YearMonthMaxCnt) + return parseTimeValue(format, MonthIndex, YearMonthMaxCnt) default: return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit) } diff --git a/types/time_test.go b/types/time_test.go index 9021c61614a36..e5a000efc0c46 100644 --- a/types/time_test.go +++ b/types/time_test.go @@ -19,6 +19,7 @@ import ( . "github.com/pingcap/check" "github.com/pingcap/parser/mysql" + "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/mock" @@ -1194,51 +1195,59 @@ func (s *testTimeSuite) TestExtractDurationNum(c *C) { c.Assert(err, ErrorMatches, "invalid unit.*") } -func (s *testTimeSuite) TestExtractTimeValue(c *C) { +func (s *testTimeSuite) TestParseDurationValue(c *C) { tbl := []struct { format string unit string res1 int64 res2 int64 res3 int64 - res4 float64 + res4 int64 + err *terror.Error }{ - {"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}, + {"52", "WEEK", 0, 0, 52 * 7, 0, nil}, + {"12", "DAY", 0, 0, 12, 0, nil}, + {"04", "MONTH", 0, 04, 0, 0, nil}, + {"1", "QUARTER", 0, 1 * 3, 0, 0, nil}, + {"2019", "YEAR", 2019, 0, 0, 0, nil}, + {"10567890", "SECOND_MICROSECOND", 0, 0, 0, 10567890000, nil}, + {"10.567890", "SECOND_MICROSECOND", 0, 0, 0, 10567890000, nil}, + {"-10.567890", "SECOND_MICROSECOND", 0, 0, 0, -10567890000, nil}, + {"35:10567890", "MINUTE_SECOND", 0, 0, 122, 29190000000000, nil}, // 122 * 3600 * 24 + 29190 = 35 * 60 + 10567890 + {"3510567890", "MINUTE_SECOND", 0, 0, 40631, 49490000000000, nil}, // 40631 * 3600 * 24 + 49490 = 3510567890 + {"11:35:10.567890", "HOUR_MICROSECOND", 0, 0, 0, 41710567890000, nil}, // = (11 * 3600 + 35 * 60) * 1000000000 + 10567890000 + {"567890", "HOUR_MICROSECOND", 0, 0, 0, 567890000, nil}, + {"14:00", "HOUR_MINUTE", 0, 0, 0, 50400000000000, nil}, + {"14", "HOUR_MINUTE", 0, 0, 0, 840000000000, nil}, + {"12 14:00:00.345", "DAY_MICROSECOND", 0, 0, 12, 50400345000000, nil}, + {"12 14:00:00", "DAY_SECOND", 0, 0, 12, 50400000000000, nil}, + {"12 14:00", "DAY_MINUTE", 0, 0, 12, 50400000000000, nil}, + {"12 14", "DAY_HOUR", 0, 0, 12, 50400000000000, nil}, + {"1:1", "DAY_HOUR", 0, 0, 1, 3600000000000, nil}, + {"aa1bb1", "DAY_HOUR", 0, 0, 1, 3600000000000, nil}, + {"-1:1", "DAY_HOUR", 0, 0, -1, -3600000000000, nil}, + {"-aa1bb1", "DAY_HOUR", 0, 0, -1, -3600000000000, nil}, + {"2019-12", "YEAR_MONTH", 2019, 12, 0, 0, nil}, + {"1 1", "YEAR_MONTH", 1, 1, 0, 0, nil}, + {"aa1bb1", "YEAR_MONTH", 1, 1, 0, 0, nil}, + {"-1 1", "YEAR_MONTH", -1, -1, 0, 0, nil}, + {"-aa1bb1", "YEAR_MONTH", -1, -1, 0, 0, nil}, + {" \t\n\r\n - aa1bb1 \t\n ", "YEAR_MONTH", -1, -1, 0, 0, nil}, + {"1.111", "MICROSECOND", 0, 0, 0, 1000, types.ErrTruncatedWrongValue}, + {"1.111", "DAY", 0, 0, 1, 0, types.ErrTruncatedWrongValue}, } 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) + comment := Commentf("Extract %v Unit %v", col.format, col.unit) + res1, res2, res3, res4, err := types.ParseDurationValue(col.unit, col.format) + c.Assert(res1, Equals, col.res1, comment) + c.Assert(res2, Equals, col.res2, comment) + c.Assert(res3, Equals, col.res3, comment) + c.Assert(res4, Equals, col.res4, comment) + if col.err == nil { + c.Assert(err, IsNil, comment) + } else { + c.Assert(col.err.Equal(err), IsTrue) + } } }