github.com/matrixorigin/matrixone@v1.2.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(scale int32) string { 47 var symbol string 48 h, m, s, ms, isNeg := t.ClockFormat() 49 if isNeg { 50 symbol = "-" 51 } 52 if scale > 0 { 53 msecInstr := fmt.Sprintf("%06d\n", ms) 54 msecInstr = msecInstr[:scale] 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(scale int32) string { 63 var symbol string 64 h, m, s, ms, isNeg := t.ClockFormat() 65 if isNeg { 66 symbol = "-" 67 } 68 if scale > 0 { 69 msecInstr := fmt.Sprintf("%06d\n", ms) 70 msecInstr = msecInstr[:scale] 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 // scale, 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, scale int32) (Time, error) { 97 s = strings.TrimSpace(s) 98 99 // separate to date&time and msec parts 100 strs := strings.Split(s, ".") 101 timeString := strs[0] 102 isNegative := false 103 day := uint64(0) 104 105 // handle date&time part 106 // If the input string have date, make sure it is valid. 107 if isDateType(timeString) { 108 // The date type format must be "yyyy-mm-dd hh:mm:ss" and 109 // it can be handled like Datetime 110 dt, err := ParseDatetime(s, scale) 111 if err != nil { 112 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 113 } 114 return dt.ToTime(scale), nil 115 } else { 116 // empty string "" equals to 00:00:00 117 if len(timeString) == 0 { 118 return Time(0), nil 119 } 120 121 if s[0] == '-' { 122 isNegative = true 123 timeString = timeString[1:] 124 } 125 126 timeParts := strings.Split(s, " ") 127 if len(timeParts) > 1 { 128 day, _ = strconv.ParseUint(timeParts[0], 10, 64) 129 if day > MaxHourInTime/maxHourInDay { 130 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 131 } 132 timeString = timeParts[1] 133 } 134 } 135 136 // handle time part 137 var hour, minute, sec uint64 138 var msec uint32 = 0 139 var carry uint32 = 0 140 var err error 141 timeArr := strings.Split(timeString, ":") 142 switch len(timeArr) { 143 case 1: // s/ss/mss/mmss/hmmss/hhmmss/...hhhmmss 144 l := len(timeArr[0]) 145 // The max length of the input is hhhhhhhhhhmmss 146 // Because the max hour and int64 with solution 147 // msec can present is 2562047787 148 if l > 14 { 149 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 150 } 151 152 parsingString := timeArr[0] 153 if l <= 2 { 154 // l <= 2: s/ss 155 if sec, err = strconv.ParseUint(parsingString[0:l], 10, 8); err != nil { 156 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 157 } 158 } else if l <= 4 { 159 // 2 < l <= 4: mss/mmss 160 // m is the length of minute part 161 minuLen := l - 2 162 if minute, err = strconv.ParseUint(parsingString[0:minuLen], 10, 8); err != nil { 163 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 164 } 165 if sec, err = strconv.ParseUint(parsingString[minuLen:l], 10, 8); err != nil { 166 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 167 } 168 } else { 169 // l > 4: hh...hhmmss 170 // hourLen is the length of hour part 171 hourLen := l - 4 172 if hour, err = strconv.ParseUint(parsingString[0:hourLen], 10, 64); err != nil { 173 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 174 } 175 if minute, err = strconv.ParseUint(parsingString[hourLen:hourLen+2], 10, 8); err != nil { 176 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 177 } 178 if sec, err = strconv.ParseUint(parsingString[hourLen+2:l], 10, 8); err != nil { 179 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 180 } 181 } 182 case 2: // h:mm / hh:mm / hh...hh:mm 183 if hour, err = strconv.ParseUint(timeArr[0], 10, 64); err != nil { 184 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 185 } 186 if minute, err = strconv.ParseUint(timeArr[1], 10, 8); err != nil { 187 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 188 } 189 sec = 0 190 case 3: // h:mm:ss / hh:mm:ss / hh...hh:mm:ss 191 if hour, err = strconv.ParseUint(timeArr[0], 10, 64); err != nil { 192 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 193 } 194 if minute, err = strconv.ParseUint(timeArr[1], 10, 8); err != nil { 195 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 196 } 197 if sec, err = strconv.ParseUint(timeArr[2], 10, 8); err != nil { 198 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 199 } 200 default: 201 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 202 } 203 204 if !ValidTime(hour, minute, sec) { 205 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 206 } 207 208 // handle msec part 209 if len(strs) > 1 { 210 msec, carry, err = getMsec(strs[1], scale) 211 if err != nil { 212 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 213 } 214 } 215 216 if day > MaxHourInTime/maxHourInDay-hour { 217 return -1, moerr.NewInvalidInputNoCtx("invalid time value %s", s) 218 } 219 220 return TimeFromClock(isNegative, hour+day*24, uint8(minute), uint8(sec+uint64(carry)), msec), nil 221 } 222 223 // Numeric 112233/112233.4444 should be treate like string and then 224 // cast into time type 225 // The integrity numeric is int64 while numeric with decimal is Decimal128 226 // The number befre the decimal point is the hh:mm:ss part while 227 // decimal part is the msec. 228 // eg. 229 // 230 // 111111112233 -> "111111112233" -> "11111111:22:33" 231 // 112233 -> "112233" -> "11:22:33" 232 // 123 -> "000123" -> "00:01:23" 233 // 112233.444 -> "112233.444" -> "11:22:33.444" 234 235 func ParseInt64ToTime(input int64, scale int32) (Time, error) { 236 if input < MinInputIntTime || input > MaxInputIntTime { 237 return -1, moerr.NewInvalidInputNoCtx("invalid time value %d", input) 238 } 239 s := strconv.FormatInt(input, 10) 240 return ParseTime(s, scale) 241 } 242 243 func ParseDecimal64ToTime(input Decimal64, scale1, scale2 int32) (Time, error) { 244 input, _ = input.Scale(scale2 - scale1) 245 s := input.Format(scale2) 246 return ParseTime(s, scale2) 247 } 248 249 func ParseDecimal128ToTime(input Decimal128, scale1, scale2 int32) (Time, error) { 250 input, _ = input.Scale(scale2 - scale1) 251 s := input.Format(scale2) 252 return ParseTime(s, scale2) 253 } 254 255 func (t Time) ToInt64() int64 { 256 h, m, s, _, isNeg := t.ClockFormat() 257 trans := int64(h*10000) + int64(m)*100 + int64(s) 258 if isNeg { 259 trans = -trans 260 } 261 262 return trans 263 } 264 265 func (t Time) ToDecimal64(ctx context.Context, width, scale int32) (Decimal64, error) { 266 tToStr := t.NumericString(scale) 267 ret, err := ParseDecimal64(tToStr, width, scale) 268 if err != nil { 269 return ret, moerr.NewInternalError(ctx, "exist time cant't cast to decimal64") 270 } 271 272 return ret, nil 273 } 274 275 func (t Time) ToDecimal128(ctx context.Context, width, scale int32) (Decimal128, error) { 276 tToStr := t.NumericString(scale) 277 ret, err := ParseDecimal128(tToStr, width, scale) 278 if err != nil { 279 return ret, moerr.NewInternalError(ctx, "exist time cant't cast to decimal128") 280 } 281 282 return ret, nil 283 } 284 285 func TimeFromClock(isNegative bool, hour uint64, minute, sec uint8, msec uint32) Time { 286 secs := int64(hour)*SecsPerHour + int64(minute)*SecsPerMinute + int64(sec) 287 t := secs*MicroSecsPerSec + int64(msec) 288 if isNegative { 289 return Time(-t) 290 } 291 return Time(t) 292 } 293 294 // ClockFormat: symbol part/hour part/minute part/second part/msecond part 295 func (t Time) ClockFormat() (hour uint64, minute, sec uint8, msec uint64, isNeg bool) { 296 if t < 0 { 297 isNeg = true 298 t = -t 299 } 300 ts := t.sec() 301 h := uint64(ts / SecsPerHour) 302 m := uint8(ts % SecsPerHour / SecsPerMinute) 303 s := uint8(ts % SecsPerMinute) 304 ms := uint64(t % MicroSecsPerSec) 305 306 return h, m, s, ms, isNeg 307 } 308 309 func (t Time) MicroSec() int64 { 310 return int64(t) % MicroSecsPerSec 311 } 312 313 func (t Time) Sec() int8 { 314 s := int8((t.sec()) % SecsPerMinute) 315 return s 316 } 317 318 func (t Time) Minute() int8 { 319 m := int8((t.sec()) % SecsPerHour / SecsPerMinute) 320 return m 321 } 322 323 func (t Time) Hour() int64 { 324 h := (t.sec()) / SecsPerHour 325 return h 326 } 327 328 // TODO: Get Today date from local time zone setting? 329 func (t Time) ToDate() Date { 330 return Today(time.UTC) 331 } 332 333 // We need to truncate the part after scale position when cast 334 // between different scale. 335 func (t Time) ToDatetime(scale int32) Datetime { 336 // TODO: Get today date from local time zone setting? 337 d := Today(time.UTC) 338 dt := d.ToDatetime() 339 if scale == 6 { 340 return Datetime(int64(dt) + int64(t)) 341 } 342 343 // TODO: add the valid check 344 newTime := Datetime(int64(dt) + int64(t)) 345 base := newTime / scaleVal[scale] 346 if newTime%scaleVal[scale]/scaleVal[scale+1] >= 5 { // check carry 347 base += 1 348 } 349 return base * scaleVal[scale] 350 } 351 352 // AddInterval now date or time use the function to add/sub date, 353 // return type bool means the if the time is valid 354 func (t Time) AddInterval(nums int64, its IntervalType) (Time, bool) { 355 switch its { 356 case Second: 357 nums *= MicroSecsPerSec 358 case Minute: 359 nums *= MicroSecsPerSec * SecsPerMinute 360 case Hour: 361 nums *= MicroSecsPerSec * SecsPerHour 362 } 363 newTime := t + Time(nums) 364 365 // valid 366 h := newTime.Hour() 367 if h < 0 { 368 h = -h 369 } 370 if !ValidTime(uint64(h), 0, 0) { 371 return 0, false 372 } 373 return newTime, true 374 } 375 376 func (t Time) ConvertToInterval(ctx context.Context, its string) (int64, error) { 377 switch its { 378 case "microsecond": 379 return int64(t), nil 380 case "second": 381 return int64(t) / MicroSecsPerSec, nil 382 case "minute": 383 return int64(t) / (MicroSecsPerSec * SecsPerMinute), nil 384 case "hour": 385 return int64(t) / (MicroSecsPerSec * SecsPerHour), nil 386 } 387 return 0, moerr.NewInvalidInput(ctx, "invalid time input") 388 } 389 390 func (t Time) sec() int64 { 391 return int64(t) / MicroSecsPerSec 392 } 393 394 func ValidTime(h, m, s uint64) bool { 395 if h < MinHourInTime || h > MaxHourInTime { 396 return false 397 } 398 if m < minMinuteInHour || m > maxMinuteInHour { 399 return false 400 } 401 if s < minSecondInMinute || s > maxSecondInMinute { 402 return false 403 } 404 return true 405 } 406 407 func isDateType(s string) bool { 408 strArr := strings.Split(s, " ") 409 if len(strArr) > 1 { 410 if _, err := strconv.ParseUint(strArr[0], 10, 64); err != nil { 411 return true 412 } 413 } 414 415 return false 416 }