github.com/matrixorigin/matrixone@v0.7.0/pkg/container/types/time.go (about) 1 // Copyright 2021 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package types 16 17 import ( 18 "context" 19 "fmt" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/matrixorigin/matrixone/pkg/common/moerr" 25 ) 26 27 const ( 28 // Time range is [-2562047787:59:59.999999,2562047787:59:59.999999] 29 // This is the max hour that int64 with solution msec can present 30 // (9223372036854775807(max int64)/1000000(msec) - 1)/3600(sec per hour) - 1 = 2562047787 31 MinHourInTime, MaxHourInTime = 0, 2562047787 32 MinInputIntTime, MaxInputIntTime = -25620477875959, 25620477875959 33 ) 34 35 // no msec part 36 // Format: hh:mm:ss 37 func (t Time) String() string { 38 h, m, s, _, isNeg := t.ClockFormat() 39 if isNeg { 40 return fmt.Sprintf("-%02d:%02d:%02d", h, m, s) 41 } 42 return fmt.Sprintf("%02d:%02d:%02d", h, m, s) 43 } 44 45 // Format: hh:mm:ss.msec 46 func (t Time) String2(precision int32) string { 47 var symbol string 48 h, m, s, ms, isNeg := t.ClockFormat() 49 if isNeg { 50 symbol = "-" 51 } 52 if precision > 0 { 53 msecInstr := fmt.Sprintf("%06d\n", ms) 54 msecInstr = msecInstr[:precision] 55 return fmt.Sprintf("%s%02d:%02d:%02d"+"."+msecInstr, symbol, h, m, s) 56 } 57 return fmt.Sprintf("%s%02d:%02d:%02d", symbol, h, m, s) 58 } 59 60 // Format: hhmmss.msec 61 // TODO: add the carry when truncate 62 func (t Time) NumericString(precision int32) string { 63 var symbol string 64 h, m, s, ms, isNeg := t.ClockFormat() 65 if isNeg { 66 symbol = "-" 67 } 68 if precision > 0 { 69 msecInstr := fmt.Sprintf("%06d\n", ms) 70 msecInstr = msecInstr[:precision] 71 return fmt.Sprintf("%s%02d%02d%02d"+"."+msecInstr, symbol, h, m, s) 72 } 73 return fmt.Sprintf("%s%02d%02d%02d", symbol, h, m, s) 74 } 75 76 // The Time type holds number of microseconds for hh:mm:ss(.msec) 77 78 // ParseTime will parse a string to a Time type 79 // Support Format: 80 // * yyyy-mm-dd hh:mm:ss(.msec) 81 // * (-)hh:mm:ss(.msec) 82 // * (-)hh:mm and 83 // * (-)hhmmss(.msec) 84 85 // During parsing, if the input length of msec is larger than predefined 86 // precision, it will be rounded 87 // eg. 88 // 89 // Time(3) input string parsing result 90 // "11:11:11.1234" "11:11:11.123" 91 // "11:11:11.1235" "11:11:11.124" 92 // "11:11:11.9994" "11:11:11.999" 93 // "11:11:11.9995" "11:11:12.000" 94 // "-11:11:11.1235" "-11:11:11.124" 95 // "-11:11:11.9995" "-11:11:12.000" 96 func ParseTime(s string, precision int32) (Time, error) { 97 s = strings.TrimSpace(s) 98 99 // seperate to date&time and msec parts 100 strs := strings.Split(s, ".") 101 timeString := strs[0] 102 isNegative := false 103 104 // handle date&time part 105 // If the input string have date, make sure it is valid. 106 if isDateType(timeString) { 107 // The date type format must be "yyyy-mm-dd hh:mm:ss" and 108 // it can be handled like Datetime 109 dt, err := ParseDatetime(s, precision) 110 if err != nil { 111 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 112 } 113 return dt.ToTime(precision), nil 114 } else { 115 // empty string "" equals to 00:00:00 116 if len(timeString) == 0 { 117 return Time(0), nil 118 } 119 120 if s[0] == '-' { 121 isNegative = true 122 timeString = timeString[1:] 123 } 124 } 125 126 // handle time part 127 var hour, minute, sec uint64 128 var msec uint32 = 0 129 var carry uint32 = 0 130 var err error 131 timeArr := strings.Split(timeString, ":") 132 switch len(timeArr) { 133 case 1: // s/ss/mss/mmss/hmmss/hhmmss/...hhhmmss 134 l := len(timeArr[0]) 135 // The max length of the input is hhhhhhhhhhmmss 136 // Because the max hour and int64 with solution 137 // msec can present is 2562047787 138 if l > 14 { 139 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 140 } 141 142 parsingString := timeArr[0] 143 if l <= 2 { 144 // l <= 2: s/ss 145 if sec, err = strconv.ParseUint(parsingString[0:l], 10, 8); err != nil { 146 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 147 } 148 } else if l <= 4 { 149 // 2 < l <= 4: mss/mmss 150 // m is the length of minute part 151 minuLen := l - 2 152 if minute, err = strconv.ParseUint(parsingString[0:minuLen], 10, 8); err != nil { 153 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 154 } 155 if sec, err = strconv.ParseUint(parsingString[minuLen:l], 10, 8); err != nil { 156 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 157 } 158 } else { 159 // l > 4: hh...hhmmss 160 // hourLen is the length of hour part 161 hourLen := l - 4 162 if hour, err = strconv.ParseUint(parsingString[0:hourLen], 10, 64); err != nil { 163 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 164 } 165 if minute, err = strconv.ParseUint(parsingString[hourLen:hourLen+2], 10, 8); err != nil { 166 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 167 } 168 if sec, err = strconv.ParseUint(parsingString[hourLen+2:l], 10, 8); err != nil { 169 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 170 } 171 } 172 case 2: // h:mm / hh:mm / hh...hh:mm 173 if hour, err = strconv.ParseUint(timeArr[0], 10, 64); err != nil { 174 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 175 } 176 if minute, err = strconv.ParseUint(timeArr[1], 10, 8); err != nil { 177 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 178 } 179 sec = 0 180 case 3: // h:mm:ss / hh:mm:ss / hh...hh:mm:ss 181 if hour, err = strconv.ParseUint(timeArr[0], 10, 64); err != nil { 182 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 183 } 184 if minute, err = strconv.ParseUint(timeArr[1], 10, 8); err != nil { 185 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 186 } 187 if sec, err = strconv.ParseUint(timeArr[2], 10, 8); err != nil { 188 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 189 } 190 default: 191 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 192 } 193 194 if !ValidTime(hour, minute, sec) { 195 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 196 } 197 198 // handle msec part 199 if len(strs) > 1 { 200 msec, carry, err = getMsec(strs[1], precision) 201 if err != nil { 202 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 203 } 204 } 205 206 return TimeFromClock(isNegative, hour, uint8(minute), uint8(sec+uint64(carry)), msec), nil 207 } 208 209 // Numeric 112233/112233.4444 should be treate like string and then 210 // cast into time type 211 // The integrity numeric is int64 while numeric with decimal is Decimal128 212 // The number befre the decimal point is the hh:mm:ss part while 213 // decimal part is the msec. 214 // eg. 215 // 216 // 111111112233 -> "111111112233" -> "11111111:22:33" 217 // 112233 -> "112233" -> "11:22:33" 218 // 123 -> "000123" -> "00:01:23" 219 // 112233.444 -> "112233.444" -> "11:22:33.444" 220 221 func ParseInt64ToTime(input int64, precision int32) (Time, error) { 222 if input < MinInputIntTime || input > MaxInputIntTime { 223 return -1, moerr.NewInvalidInputNoCtx("invalid time value %d", input) 224 } 225 s := strconv.FormatInt(input, 10) 226 return ParseTime(s, precision) 227 } 228 229 func ParseDecimal64lToTime(input Decimal64, precision int32) (Time, error) { 230 s := input.ToStringWithScale(precision) 231 return ParseTime(s, precision) 232 } 233 234 func ParseDecimal128lToTime(input Decimal128, precision int32) (Time, error) { 235 s := input.ToStringWithScale(precision) 236 return ParseTime(s, precision) 237 } 238 239 func (t Time) ToInt64() int64 { 240 h, m, s, _, isNeg := t.ClockFormat() 241 trans := int64(h*10000) + int64(m)*100 + int64(s) 242 if isNeg { 243 trans = -trans 244 } 245 246 return trans 247 } 248 249 func (t Time) ToDecimal64(ctx context.Context, width, precision int32) (Decimal64, error) { 250 tToStr := t.NumericString(precision) 251 ret, err := ParseStringToDecimal64(tToStr, width, precision, false) 252 if err != nil { 253 return ret, moerr.NewInternalError(ctx, "exsit time cant't cast to decimal64") 254 } 255 256 return ret, nil 257 } 258 259 func (t Time) ToDecimal128(ctx context.Context, width, precision int32) (Decimal128, error) { 260 tToStr := t.NumericString(precision) 261 ret, err := ParseStringToDecimal128(tToStr, width, precision, false) 262 if err != nil { 263 return ret, moerr.NewInternalError(ctx, "exsit time cant't cast to decimal128") 264 } 265 266 return ret, nil 267 } 268 269 func TimeFromClock(isNegative bool, hour uint64, minute, sec uint8, msec uint32) Time { 270 secs := int64(hour)*secsPerHour + int64(minute)*secsPerMinute + int64(sec) 271 t := secs*microSecsPerSec + int64(msec) 272 if isNegative { 273 return Time(-t) 274 } 275 return Time(t) 276 } 277 278 // ClockFormat: symbol part/hour part/minute part/second part/msecond part 279 func (t Time) ClockFormat() (hour uint64, minute, sec uint8, msec uint64, isNeg bool) { 280 if t < 0 { 281 isNeg = true 282 t = -t 283 } 284 ts := t.sec() 285 h := uint64(ts / secsPerHour) 286 m := uint8(ts % secsPerHour / secsPerMinute) 287 s := uint8(ts % secsPerMinute) 288 ms := uint64(t % microSecsPerSec) 289 290 return h, m, s, ms, isNeg 291 } 292 293 func (t Time) MicroSec() int64 { 294 return int64(t) % microSecsPerSec 295 } 296 297 func (t Time) Sec() int8 { 298 s := int8((t.sec()) % secsPerMinute) 299 return s 300 } 301 302 func (t Time) Minute() int8 { 303 m := int8((t.sec()) % secsPerHour / secsPerMinute) 304 return m 305 } 306 307 func (t Time) Hour() int64 { 308 h := (t.sec()) / secsPerHour 309 return h 310 } 311 312 // TODO: Get Today date from local time zone setting? 313 func (t Time) ToDate() Date { 314 return Today(time.UTC) 315 } 316 317 // We need to truncate the part after precision position when cast 318 // between different precision. 319 func (t Time) ToDatetime(precision int32) Datetime { 320 // TODO: Get today date from local time zone setting? 321 d := Today(time.UTC) 322 dt := d.ToDatetime() 323 if precision == 6 { 324 return Datetime(int64(dt) + int64(t)) 325 } 326 327 // TODO: add the valid check 328 newTime := Datetime(int64(dt) + int64(t)) 329 base := newTime / precisionVal[precision] 330 if newTime%precisionVal[precision]/precisionVal[precision+1] >= 5 { // check carry 331 base += 1 332 } 333 return base * precisionVal[precision] 334 } 335 336 // AddInterval now date or time use the function to add/sub date, 337 // return type bool means the if the time is valid 338 func (t Time) AddInterval(nums int64, its IntervalType) (Time, bool) { 339 switch its { 340 case Second: 341 nums *= microSecsPerSec 342 case Minute: 343 nums *= microSecsPerSec * secsPerMinute 344 case Hour: 345 nums *= microSecsPerSec * secsPerHour 346 } 347 newTime := t + Time(nums) 348 349 // valid 350 h := newTime.Hour() 351 if h < 0 { 352 h = -h 353 } 354 if !ValidTime(uint64(h), 0, 0) { 355 return 0, false 356 } 357 return newTime, true 358 } 359 360 func (t Time) ConvertToInterval(ctx context.Context, its string) (int64, error) { 361 switch its { 362 case "microsecond": 363 return int64(t), nil 364 case "second": 365 return int64(t) / microSecsPerSec, nil 366 case "minute": 367 return int64(t) / (microSecsPerSec * secsPerMinute), nil 368 case "hour": 369 return int64(t) / (microSecsPerSec * secsPerHour), nil 370 } 371 return 0, moerr.NewInvalidInput(ctx, "invalid time input") 372 } 373 374 func (t Time) sec() int64 { 375 return int64(t) / microSecsPerSec 376 } 377 378 func ValidTime(h, m, s uint64) bool { 379 if h < MinHourInTime || h > MaxHourInTime { 380 return false 381 } 382 if m < minMinuteInHour || m > maxMinuteInHour { 383 return false 384 } 385 if s < minSecondInMinute || s > maxSecondInMinute { 386 return false 387 } 388 return true 389 } 390 391 func isDateType(s string) bool { 392 strArr := strings.Split(s, " ") 393 return len(strArr) > 1 394 }