diff --git a/pkg/container/types/timestamp.go b/pkg/container/types/timestamp.go index e10845d80eaf6df4cacf6775983b04284b7530a4..92fb2e76d2c6538982b7684b4c0ad54804eeff80 100644 --- a/pkg/container/types/timestamp.go +++ b/pkg/container/types/timestamp.go @@ -35,12 +35,23 @@ package types import ( "fmt" + "github.com/matrixorigin/matrixone/pkg/errno" + "github.com/matrixorigin/matrixone/pkg/sql/errors" "strconv" "unsafe" ) const microSecondsDigits = 6 +var TimestampMinValue Timestamp +var TimestampMaxValue Timestamp + +// the range for TIMESTAMP values is '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'. +func init() { + TimestampMinValue = FromClockUTC(1970, 1, 1, 0, 0, 1, 0) + TimestampMaxValue = FromClockUTC(2038, 1, 19, 3, 14, 07, 999999) +} + func (ts Timestamp) String() string { dt := Datetime(int64(ts) + localTZ<<20) y, m, d, _ := dt.ToDate().Calendar(true) @@ -65,6 +76,51 @@ func (ts Timestamp) String2(precision int32) string { return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", y, m, d, hour, minute, sec) } +var ( + errIncorrectTimestampValue = errors.New(errno.DataException, "Incorrect timestamp value") + errTimestampOutOfRange = errors.New(errno.DataException, "timestamp out of range") +) + +// this scaleTable stores the corresponding microseconds value for a precision +var scaleTable = [...]uint32{1000000, 100000, 10000, 1000, 100, 10, 1} + +var OneSecInMicroSeconds = uint32(1000000) + +func getMsec(msecStr string, precision int32) (uint32, uint32, error) { + msecs := uint32(0) + carry := uint32(0) + msecCarry := uint32(0) + if len(msecStr) > int(precision) { + if msecStr[precision] >= '5' && msecStr[precision] <= '9' { + msecCarry = 1 + } else if msecStr[precision] >= '0' && msecStr[precision] <= '5' { + msecCarry = 0 + } else { + return 0, 0, errIncorrectDatetimeValue + } + msecStr = msecStr[:precision] + } else if len(msecStr) < int(precision) { + lengthMsecStr := len(msecStr) + padZeros := int(precision) - lengthMsecStr + for i := 0; i < padZeros; i++ { + msecStr = msecStr + string('0') + } + } + if len(msecStr) == 0 { // this means the precision is 0 + return 0, msecCarry, nil + } + m, err := strconv.ParseUint(msecStr, 10, 32) + if err != nil { + return 0, 0, errIncorrectTimestampValue + } + msecs = (uint32(m) + msecCarry) * scaleTable[precision] + if msecs == OneSecInMicroSeconds { + carry = 1 + msecs = 0 + } + return msecs, carry, nil +} + // ParseTimestamp will parse a string to be a Timestamp // Support Format: // 1. all the Date value @@ -75,60 +131,53 @@ func ParseTimestamp(s string, precision int32) (Timestamp, error) { if d, err := ParseDate(s); err == nil { return Timestamp(d.ToTime()), nil } - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } var year int32 var month, day, hour, minute, second uint8 var msec uint32 = 0 + var carry uint32 = 0 + var err error year = int32(s[0]-'0')*1000 + int32(s[1]-'0')*100 + int32(s[2]-'0')*10 + int32(s[3]-'0') if s[4] == '-' { if len(s) < 19 { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } month = (s[5]-'0')*10 + (s[6] - '0') if s[7] != '-' { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } day = (s[8]-'0')*10 + (s[9] - '0') if s[10] != ' ' { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } if !validDate(year, month, day) { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } hour = (s[11]-'0')*10 + (s[12] - '0') if s[13] != ':' { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } minute = (s[14]-'0')*10 + (s[15] - '0') if s[16] != ':' { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } second = (s[17]-'0')*10 + (s[18] - '0') if !validTimeInDay(hour, minute, second) { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } if len(s) > 19 { // for a timestamp string like "2020-01-01 11:11:11.123" // the microseconds part .123 should be interpreted as 123000 microseconds, so we need to pad zeros if len(s) > 20 && s[19] == '.' { msecStr := s[20:] - lengthMsecStr := len(msecStr) - if lengthMsecStr > int(precision) { - msecStr = msecStr[:precision] - } - padZeros := microSecondsDigits - len(msecStr) - for i := 0; i < padZeros; i++ { - msecStr = msecStr + string('0') - } - m, err := strconv.ParseUint(msecStr, 10, 32) + msec, carry, err = getMsec(msecStr, precision) if err != nil { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } - msec = uint32(m) } else { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } } } else { @@ -142,26 +191,20 @@ func ParseTimestamp(s string, precision int32) (Timestamp, error) { // the microseconds part .123 should be interpreted as 123000 microseconds, so we need to pad zeros if len(s) > 15 && s[14] == '.' { msecStr := s[15:] - lengthMsecStr := len(msecStr) - if lengthMsecStr > microSecondsDigits { - msecStr = msecStr[:microSecondsDigits] - } else { - padZeros := microSecondsDigits - lengthMsecStr - for i := 0; i < padZeros; i++ { - msecStr = msecStr + string('0') - } - } - m, err := strconv.ParseUint(msecStr, 10, 32) + msec, carry, err = getMsec(msecStr, precision) if err != nil { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } - msec = uint32(m) } else { - return -1, errIncorrectDatetimeValue + return -1, errIncorrectTimestampValue } } } - result := FromClockUTC(year, month, day, hour, minute, second, msec) + + result := FromClockUTC(year, month, day, hour, minute, second+uint8(carry), msec) + if result > TimestampMaxValue || result < TimestampMinValue { + return -1, errTimestampOutOfRange + } return result, nil } diff --git a/pkg/container/types/temestamp_test.go b/pkg/container/types/timestamp_test.go similarity index 77% rename from pkg/container/types/temestamp_test.go rename to pkg/container/types/timestamp_test.go index db458fe46ef03b86e5a2c6bddd220d17699cd6d5..0476475b443bb9a8ddb517d7cdcc1ec71fd66e75 100644 --- a/pkg/container/types/temestamp_test.go +++ b/pkg/container/types/timestamp_test.go @@ -46,27 +46,8 @@ func TestTimestamp_String(t *testing.T) { require.NoError(t, err) require.Equal(t, "2012-01-01 11:11:11.123000", resultStr3) - resultStr4 := a.String2(3) - require.NoError(t, err) - require.Equal(t, "2012-01-01 11:11:11.123", resultStr4) - - resultStr5 := a.String2(6) - require.NoError(t, err) - require.Equal(t, "2012-01-01 11:11:11.123000", resultStr5) - - a, err = ParseTimestamp("2012-01-01 11:11:11.123456", 3) - resultStr6 := a.String2(0) - require.NoError(t, err) - require.Equal(t, "2012-01-01 11:11:11", resultStr6) - - resultStr7 := a.String2(3) - require.NoError(t, err) - require.Equal(t, "2012-01-01 11:11:11.123", resultStr7) - - resultStr8 := a.String2(6) - require.NoError(t, err) - require.Equal(t, "2012-01-01 11:11:11.123000", resultStr8) } + func TestTimestamp_String2(t *testing.T) { a, err := ParseTimestamp("2012-01-01 11:11:11", 6) require.NoError(t, err) @@ -117,19 +98,37 @@ func TestTimestamp_String2(t *testing.T) { } func TestParseTimestamp(t *testing.T) { - a, err := ParseTimestamp("0001-01-01 00:00:00", 6) + a, err := ParseTimestamp("1970-01-01 00:00:01", 6) + require.NoError(t, err) + require.Equal(t, int64(TimestampMinValue), int64(a)) + + a, err = ParseTimestamp("1970-01-01 00:00:01.123", 6) + require.NoError(t, err) + require.Equal(t, int64(TimestampMinValue+123000), int64(a)) + + a, err = ParseTimestamp("1970-01-01 00:00:01.123456", 6) require.NoError(t, err) - require.Equal(t, int64(a)+(localTZ<<20), int64(0)) + require.Equal(t, int64(TimestampMinValue+123456), int64(a)) - a, err = ParseTimestamp("0001-01-01 00:00:00.123", 6) + a, err = ParseTimestamp("1970-01-01 00:00:01.123456", 3) require.NoError(t, err) - require.Equal(t, int64(a)+(localTZ<<20), int64(123000)) + require.Equal(t, int64(a), int64(TimestampMinValue+123000)) - a, err = ParseTimestamp("0001-01-01 00:00:00.123456", 6) + a, err = ParseTimestamp("1970-01-01 00:00:01.12356", 3) require.NoError(t, err) - require.Equal(t, int64(a)+(localTZ<<20), int64(123456)) + require.Equal(t, int64(TimestampMinValue+124000), int64(a)) - a, err = ParseTimestamp("0001-01-01 00:00:00.123456", 3) + a, err = ParseTimestamp("1970-01-01 00:00:01.12345", 0) require.NoError(t, err) - require.Equal(t, int64(a)+(localTZ<<20), int64(123000)) + require.Equal(t, int64(TimestampMinValue), int64(a)) + + a, err = ParseTimestamp("1970-01-01 00:00:01.52345", 0) + require.NoError(t, err) + require.Equal(t, int64(TimestampMinValue+1<<20), int64(a)) + + a, err = ParseTimestamp("1966-01-01 00:00:01.52345", 0) + require.Error(t, err) + + a, err = ParseTimestamp("2966-01-01 00:00:01.52345", 0) + require.Error(t, err) }