github.com/dolthub/go-mysql-server@v0.18.0/sql/types/time.go (about) 1 // Copyright 2022 Dolthub, Inc. 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 "fmt" 19 "math" 20 "reflect" 21 "strconv" 22 "strings" 23 "time" 24 25 "github.com/dolthub/vitess/go/sqltypes" 26 "github.com/dolthub/vitess/go/vt/proto/query" 27 "github.com/shopspring/decimal" 28 "gopkg.in/src-d/go-errors.v1" 29 30 "github.com/dolthub/go-mysql-server/sql" 31 ) 32 33 var ( 34 Time TimeType = TimespanType_{} 35 36 ErrConvertingToTimeType = errors.NewKind("value %v is not a valid Time") 37 38 timespanMinimum int64 = -3020399000000 39 timespanMaximum int64 = 3020399000000 40 microsecondsPerSecond int64 = 1000000 41 microsecondsPerMinute int64 = 60000000 42 microsecondsPerHour int64 = 3600000000 43 nanosecondsPerMicrosecond int64 = 1000 44 45 timeValueType = reflect.TypeOf(Timespan(0)) 46 ) 47 48 // TimeType represents the TIME type. 49 // https://dev.mysql.com/doc/refman/8.0/en/time.html 50 // TIME is implemented as TIME(6). 51 // The type of the returned value is Timespan. 52 // TODO: implement parameters on the TIME type 53 type TimeType interface { 54 sql.Type 55 // ConvertToTimespan returns a Timespan from the given interface. Follows the same conversion rules as 56 // Convert(), in that this will process the value based on its base-10 visual representation (for example, Convert() 57 // will interpret the value `1234` as 12 minutes and 34 seconds). Returns an error for nil values. 58 ConvertToTimespan(v interface{}) (Timespan, error) 59 // ConvertToTimeDuration returns a time.Duration from the given interface. Follows the same conversion rules as 60 // Convert(), in that this will process the value based on its base-10 visual representation (for example, Convert() 61 // will interpret the value `1234` as 12 minutes and 34 seconds). Returns an error for nil values. 62 ConvertToTimeDuration(v interface{}) (time.Duration, error) 63 // MicrosecondsToTimespan returns a Timespan from the given number of microseconds. This differs from Convert(), as 64 // that will process the value based on its base-10 visual representation (for example, Convert() will interpret 65 // the value `1234` as 12 minutes and 34 seconds). This clamps the given microseconds to the allowed range. 66 MicrosecondsToTimespan(v int64) Timespan 67 } 68 69 type TimespanType_ struct{} 70 71 var _ TimeType = TimespanType_{} 72 var _ sql.CollationCoercible = TimespanType_{} 73 74 // MaxTextResponseByteLength implements the Type interface 75 func (t TimespanType_) MaxTextResponseByteLength(_ *sql.Context) uint32 { 76 // 10 digits are required for a text representation without microseconds, but with microseconds 77 // requires 17, so return 17 as an upper limit (i.e. len(+123:00:00.999999")) 78 return 17 79 } 80 81 // Timespan is the value type returned by TimeType.Convert(). 82 type Timespan int64 83 84 // Compare implements Type interface. 85 func (t TimespanType_) Compare(a interface{}, b interface{}) (int, error) { 86 if hasNulls, res := CompareNulls(a, b); hasNulls { 87 return res, nil 88 } 89 90 as, err := t.ConvertToTimespan(a) 91 if err != nil { 92 return 0, err 93 } 94 bs, err := t.ConvertToTimespan(b) 95 if err != nil { 96 return 0, err 97 } 98 99 return as.Compare(bs), nil 100 } 101 102 func (t TimespanType_) Convert(v interface{}) (interface{}, sql.ConvertInRange, error) { 103 if v == nil { 104 return nil, sql.InRange, nil 105 } 106 ret, err := t.ConvertToTimespan(v) 107 return ret, sql.InRange, err 108 } 109 110 // MustConvert implements the Type interface. 111 func (t TimespanType_) MustConvert(v interface{}) interface{} { 112 value, _, err := t.Convert(v) 113 if err != nil { 114 panic(err) 115 } 116 return value 117 } 118 119 // ConvertToTimespan converts the given interface value to a Timespan. This follows the conversion rules of MySQL, which 120 // are based on the base-10 visual representation of numbers (for example, Time.Convert() will interpret the value 121 // `1234` as 12 minutes and 34 seconds). Returns an error on a nil value. 122 func (t TimespanType_) ConvertToTimespan(v interface{}) (Timespan, error) { 123 switch value := v.(type) { 124 case Timespan: 125 // We only create a Timespan if it's valid, so we can skip this check if we receive a Timespan. 126 // Timespan values are not intended to be modified by an integrator, therefore it is on the integrator if they corrupt a Timespan. 127 return value, nil 128 case int: 129 return t.ConvertToTimespan(int64(value)) 130 case uint: 131 return t.ConvertToTimespan(int64(value)) 132 case int8: 133 return t.ConvertToTimespan(int64(value)) 134 case uint8: 135 return t.ConvertToTimespan(int64(value)) 136 case int16: 137 return t.ConvertToTimespan(int64(value)) 138 case uint16: 139 return t.ConvertToTimespan(int64(value)) 140 case int32: 141 return t.ConvertToTimespan(int64(value)) 142 case uint32: 143 return t.ConvertToTimespan(int64(value)) 144 case int64: 145 absValue := int64Abs(value) 146 if absValue >= -59 && absValue <= 59 { 147 return t.MicrosecondsToTimespan(value * microsecondsPerSecond), nil 148 } else if absValue >= 100 && absValue <= 9999 { 149 minutes := absValue / 100 150 seconds := absValue % 100 151 if minutes <= 59 && seconds <= 59 { 152 microseconds := (seconds * microsecondsPerSecond) + (minutes * microsecondsPerMinute) 153 if value < 0 { 154 return t.MicrosecondsToTimespan(-1 * microseconds), nil 155 } 156 return t.MicrosecondsToTimespan(microseconds), nil 157 } 158 } else if absValue >= 10000 && absValue <= 9999999 { 159 hours := absValue / 10000 160 minutes := (absValue / 100) % 100 161 seconds := absValue % 100 162 if minutes <= 59 && seconds <= 59 { 163 microseconds := (seconds * microsecondsPerSecond) + (minutes * microsecondsPerMinute) + (hours * microsecondsPerHour) 164 if value < 0 { 165 return t.MicrosecondsToTimespan(-1 * microseconds), nil 166 } 167 return t.MicrosecondsToTimespan(microseconds), nil 168 } 169 } 170 case uint64: 171 return t.ConvertToTimespan(int64(value)) 172 case float32: 173 return t.ConvertToTimespan(float64(value)) 174 case float64: 175 intValue := int64(value) 176 microseconds := int64Abs(int64(math.Round((value - float64(intValue)) * float64(microsecondsPerSecond)))) 177 absValue := int64Abs(intValue) 178 if absValue >= -59 && absValue <= 59 { 179 totalMicroseconds := (absValue * microsecondsPerSecond) + microseconds 180 if value < 0 { 181 return t.MicrosecondsToTimespan(-1 * totalMicroseconds), nil 182 } 183 return t.MicrosecondsToTimespan(totalMicroseconds), nil 184 } else if absValue >= 100 && absValue <= 9999 { 185 minutes := absValue / 100 186 seconds := absValue % 100 187 if minutes <= 59 && seconds <= 59 { 188 totalMicroseconds := (seconds * microsecondsPerSecond) + (minutes * microsecondsPerMinute) + microseconds 189 if value < 0 { 190 return t.MicrosecondsToTimespan(-1 * totalMicroseconds), nil 191 } 192 return t.MicrosecondsToTimespan(totalMicroseconds), nil 193 } 194 } else if absValue >= 10000 && absValue <= 9999999 { 195 hours := absValue / 10000 196 minutes := (absValue / 100) % 100 197 seconds := absValue % 100 198 if minutes <= 59 && seconds <= 59 { 199 totalMicroseconds := (seconds * microsecondsPerSecond) + (minutes * microsecondsPerMinute) + (hours * microsecondsPerHour) + microseconds 200 if value < 0 { 201 return t.MicrosecondsToTimespan(-1 * totalMicroseconds), nil 202 } 203 return t.MicrosecondsToTimespan(totalMicroseconds), nil 204 } 205 } 206 case decimal.Decimal: 207 return t.ConvertToTimespan(value.IntPart()) 208 case decimal.NullDecimal: 209 if value.Valid { 210 return t.ConvertToTimespan(value.Decimal.IntPart()) 211 } 212 case string: 213 impl, err := stringToTimespan(value) 214 if err == nil { 215 return impl, nil 216 } 217 if strings.Contains(value, ".") { 218 strAsDouble, err := strconv.ParseFloat(value, 64) 219 if err != nil { 220 return Timespan(0), ErrConvertingToTimeType.New(v) 221 } 222 return t.ConvertToTimespan(strAsDouble) 223 } else { 224 strAsInt, err := strconv.ParseInt(value, 10, 64) 225 if err != nil { 226 return Timespan(0), ErrConvertingToTimeType.New(v) 227 } 228 return t.ConvertToTimespan(strAsInt) 229 } 230 case time.Duration: 231 microseconds := value.Nanoseconds() / nanosecondsPerMicrosecond 232 return t.MicrosecondsToTimespan(microseconds), nil 233 case time.Time: 234 h, m, s := value.Clock() 235 us := int64(value.Nanosecond())/nanosecondsPerMicrosecond + 236 microsecondsPerSecond*int64(s) + 237 microsecondsPerMinute*int64(m) + 238 microsecondsPerHour*int64(h) 239 return Timespan(us), nil 240 } 241 242 return Timespan(0), ErrConvertingToTimeType.New(v) 243 } 244 245 // ConvertToTimeDuration implements the TimeType interface. 246 func (t TimespanType_) ConvertToTimeDuration(v interface{}) (time.Duration, error) { 247 val, err := t.ConvertToTimespan(v) 248 if err != nil { 249 return time.Duration(0), err 250 } 251 return val.AsTimeDuration(), nil 252 } 253 254 // Equals implements the Type interface. 255 func (t TimespanType_) Equals(otherType sql.Type) bool { 256 _, ok := otherType.(TimespanType_) 257 return ok 258 } 259 260 // Promote implements the Type interface. 261 func (t TimespanType_) Promote() sql.Type { 262 return t 263 } 264 265 // SQL implements Type interface. 266 func (t TimespanType_) SQL(ctx *sql.Context, dest []byte, v interface{}) (sqltypes.Value, error) { 267 if v == nil { 268 return sqltypes.NULL, nil 269 } 270 ti, err := t.ConvertToTimespan(v) 271 if err != nil { 272 return sqltypes.Value{}, err 273 } 274 275 val := AppendAndSliceString(dest, ti.String()) 276 277 return sqltypes.MakeTrusted(sqltypes.Time, val), nil 278 } 279 280 // String implements Type interface. 281 func (t TimespanType_) String() string { 282 return "time(6)" 283 } 284 285 // Type implements Type interface. 286 func (t TimespanType_) Type() query.Type { 287 return sqltypes.Time 288 } 289 290 // ValueType implements Type interface. 291 func (t TimespanType_) ValueType() reflect.Type { 292 return timeValueType 293 } 294 295 // Zero implements Type interface. 296 func (t TimespanType_) Zero() interface{} { 297 return Timespan(0) 298 } 299 300 // CollationCoercibility implements sql.CollationCoercible interface. 301 func (TimespanType_) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 302 return sql.Collation_binary, 5 303 } 304 305 // No built in for absolute values on int64 306 func int64Abs(v int64) int64 { 307 shift := v >> 63 308 return (v ^ shift) - shift 309 } 310 311 func stringToTimespan(s string) (Timespan, error) { 312 var negative bool 313 var hours int16 314 var minutes int8 315 var seconds int8 316 var microseconds int32 317 318 if len(s) > 0 && s[0] == '-' { 319 negative = true 320 s = s[1:] 321 } 322 323 comps := strings.SplitN(s, ".", 2) 324 325 // Parse microseconds 326 if len(comps) == 2 { 327 microStr := comps[1] 328 if len(microStr) < 6 { 329 microStr += strings.Repeat("0", 6-len(comps[1])) 330 } 331 microStr, remainStr := microStr[0:6], microStr[6:] 332 convertedMicroseconds, err := strconv.Atoi(microStr) 333 if err != nil { 334 return Timespan(0), ErrConvertingToTimeType.New(s) 335 } 336 // MySQL just uses the last digit to round up. This is weird, but matches their implementation. 337 if len(remainStr) > 0 && remainStr[len(remainStr)-1:] >= "5" { 338 convertedMicroseconds++ 339 } 340 microseconds = int32(convertedMicroseconds) 341 } 342 343 // Parse H-M-S time 344 hmsComps := strings.SplitN(comps[0], ":", 3) 345 hms := make([]string, 3) 346 if len(hmsComps) >= 2 { 347 if len(hmsComps[0]) > 3 { 348 return Timespan(0), ErrConvertingToTimeType.New(s) 349 } 350 hms[0] = hmsComps[0] 351 if len(hmsComps[1]) > 2 { 352 return Timespan(0), ErrConvertingToTimeType.New(s) 353 } 354 hms[1] = hmsComps[1] 355 if len(hmsComps) == 3 { 356 if len(hmsComps[2]) > 2 { 357 return Timespan(0), ErrConvertingToTimeType.New(s) 358 } 359 hms[2] = hmsComps[2] 360 } 361 } else { 362 l := len(hmsComps[0]) 363 hms[2] = safeSubstr(hmsComps[0], l-2, l) 364 hms[1] = safeSubstr(hmsComps[0], l-4, l-2) 365 hms[0] = safeSubstr(hmsComps[0], l-7, l-4) 366 } 367 368 hmsHours, err := strconv.Atoi(hms[0]) 369 if len(hms[0]) > 0 && err != nil { 370 return Timespan(0), ErrConvertingToTimeType.New(s) 371 } 372 hours = int16(hmsHours) 373 374 hmsMinutes, err := strconv.Atoi(hms[1]) 375 if len(hms[1]) > 0 && err != nil { 376 return Timespan(0), ErrConvertingToTimeType.New(s) 377 } else if hmsMinutes >= 60 { 378 return Timespan(0), ErrConvertingToTimeType.New(s) 379 } 380 minutes = int8(hmsMinutes) 381 382 hmsSeconds, err := strconv.Atoi(hms[2]) 383 if len(hms[2]) > 0 && err != nil { 384 return Timespan(0), ErrConvertingToTimeType.New(s) 385 } else if hmsSeconds >= 60 { 386 return Timespan(0), ErrConvertingToTimeType.New(s) 387 } 388 seconds = int8(hmsSeconds) 389 390 if microseconds == int32(microsecondsPerSecond) { 391 microseconds = 0 392 seconds++ 393 } 394 if seconds == 60 { 395 seconds = 0 396 minutes++ 397 } 398 if minutes == 60 { 399 minutes = 0 400 hours++ 401 } 402 403 if hours > 838 { 404 hours = 838 405 minutes = 59 406 seconds = 59 407 } 408 409 if hours == 838 && minutes == 59 && seconds == 59 { 410 microseconds = 0 411 } 412 413 return unitsToTimespan(negative, hours, minutes, seconds, microseconds), nil 414 } 415 416 func safeSubstr(s string, start int, end int) string { 417 if start < 0 { 418 start = 0 419 } 420 if end < 0 { 421 end = 0 422 } 423 if start > len(s) { 424 start = len(s) 425 end = len(s) 426 } else if end > len(s) { 427 end = len(s) 428 } 429 return s[start:end] 430 } 431 432 // MicrosecondsToTimespan implements the TimeType interface. 433 func (_ TimespanType_) MicrosecondsToTimespan(v int64) Timespan { 434 if v < timespanMinimum { 435 v = timespanMinimum 436 } else if v > timespanMaximum { 437 v = timespanMaximum 438 } 439 return Timespan(v) 440 } 441 442 func unitsToTimespan(isNegative bool, hours int16, minutes int8, seconds int8, microseconds int32) Timespan { 443 negative := int64(1) 444 if isNegative { 445 negative = -1 446 } 447 return Timespan(negative * 448 (int64(microseconds) + 449 (int64(seconds) * microsecondsPerSecond) + 450 (int64(minutes) * microsecondsPerMinute) + 451 (int64(hours) * microsecondsPerHour))) 452 } 453 454 func (t Timespan) timespanToUnits() (isNegative bool, hours int16, minutes int8, seconds int8, microseconds int32) { 455 isNegative = t < 0 456 absV := int64Abs(int64(t)) 457 hours = int16(absV / microsecondsPerHour) 458 minutes = int8((absV / microsecondsPerMinute) % 60) 459 seconds = int8((absV / microsecondsPerSecond) % 60) 460 microseconds = int32(absV % microsecondsPerSecond) 461 return 462 } 463 464 // String returns the Timespan formatted as a string (such as for display purposes). 465 func (t Timespan) String() string { 466 isNegative, hours, minutes, seconds, microseconds := t.timespanToUnits() 467 sign := "" 468 if isNegative { 469 sign = "-" 470 } 471 if microseconds == 0 { 472 return fmt.Sprintf("%v%02d:%02d:%02d", sign, hours, minutes, seconds) 473 } 474 return fmt.Sprintf("%v%02d:%02d:%02d.%06d", sign, hours, minutes, seconds, microseconds) 475 } 476 477 // AsMicroseconds returns the Timespan in microseconds. 478 func (t Timespan) AsMicroseconds() int64 { 479 // Timespan already being implemented in microseconds is an implementation detail that integrators do not need to 480 // know about. This is also the reason for the comparison functions. 481 return int64(t) 482 } 483 484 // AsTimeDuration returns the Timespan as a time.Duration. 485 func (t Timespan) AsTimeDuration() time.Duration { 486 return time.Duration(t.AsMicroseconds() * nanosecondsPerMicrosecond) 487 } 488 489 // Equals returns whether the calling Timespan and given Timespan are equivalent. 490 func (t Timespan) Equals(other Timespan) bool { 491 return t == other 492 } 493 494 // Compare returns an integer comparing two values. The result will be 0 if t==other, -1 if t < other, and +1 if t > other. 495 func (t Timespan) Compare(other Timespan) int { 496 if t < other { 497 return -1 498 } else if t > other { 499 return 1 500 } 501 return 0 502 } 503 504 // Negate returns a new Timespan that has been negated. 505 func (t Timespan) Negate() Timespan { 506 return -1 * t 507 } 508 509 // Add returns a new Timespan that is the sum of the calling Timespan and given Timespan. The resulting Timespan is 510 // clamped to the allowed range. 511 func (t Timespan) Add(other Timespan) Timespan { 512 v := int64(t + other) 513 if v < timespanMinimum { 514 v = timespanMinimum 515 } else if v > timespanMaximum { 516 v = timespanMaximum 517 } 518 return Timespan(v) 519 } 520 521 // Subtract returns a new Timespan that is the difference of the calling Timespan and given Timespan. The resulting 522 // Timespan is clamped to the allowed range. 523 func (t Timespan) Subtract(other Timespan) Timespan { 524 v := int64(t - other) 525 if v < timespanMinimum { 526 v = timespanMinimum 527 } else if v > timespanMaximum { 528 v = timespanMaximum 529 } 530 return Timespan(v) 531 }