github.com/XiaoMi/Gaea@v1.2.5/parser/tidb-types/mytime.go (about) 1 // Copyright 2016 PingCAP, 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package types 15 16 import ( 17 gotime "time" 18 19 "github.com/pingcap/errors" 20 ) 21 22 // MysqlTime is the internal struct type for Time. 23 type MysqlTime struct { 24 year uint16 // year <= 9999 25 month uint8 // month <= 12 26 day uint8 // day <= 31 27 // When it's type is Time, HH:MM:SS may be 839:59:59, so use int to avoid overflow. 28 hour int // hour <= 23 29 minute uint8 // minute <= 59 30 second uint8 // second <= 59 31 microsecond uint32 32 } 33 34 // Year returns the year value. 35 func (t MysqlTime) Year() int { 36 return int(t.year) 37 } 38 39 // Month returns the month value. 40 func (t MysqlTime) Month() int { 41 return int(t.month) 42 } 43 44 // Day returns the day value. 45 func (t MysqlTime) Day() int { 46 return int(t.day) 47 } 48 49 // Hour returns the hour value. 50 func (t MysqlTime) Hour() int { 51 return int(t.hour) 52 } 53 54 // Minute returns the minute value. 55 func (t MysqlTime) Minute() int { 56 return int(t.minute) 57 } 58 59 // Second returns the second value. 60 func (t MysqlTime) Second() int { 61 return int(t.second) 62 } 63 64 // Microsecond returns the microsecond value. 65 func (t MysqlTime) Microsecond() int { 66 return int(t.microsecond) 67 } 68 69 // Weekday returns the Weekday value. 70 func (t MysqlTime) Weekday() gotime.Weekday { 71 // TODO: Consider time_zone variable. 72 t1, err := t.GoTime(gotime.Local) 73 if err != nil { 74 return 0 75 } 76 return t1.Weekday() 77 } 78 79 // YearDay returns day in year. 80 func (t MysqlTime) YearDay() int { 81 if t.month == 0 || t.day == 0 { 82 return 0 83 } 84 return calcDaynr(int(t.year), int(t.month), int(t.day)) - 85 calcDaynr(int(t.year), 1, 1) + 1 86 } 87 88 // YearWeek return year and week. 89 func (t MysqlTime) YearWeek(mode int) (int, int) { 90 behavior := weekMode(mode) | weekBehaviourYear 91 return calcWeek(&t, behavior) 92 } 93 94 // Week returns the week value. 95 func (t MysqlTime) Week(mode int) int { 96 if t.month == 0 || t.day == 0 { 97 return 0 98 } 99 _, week := calcWeek(&t, weekMode(mode)) 100 return week 101 } 102 103 // GoTime converts MysqlTime to GoTime. 104 func (t MysqlTime) GoTime(loc *gotime.Location) (gotime.Time, error) { 105 // gotime.Time can't represent month 0 or day 0, date contains 0 would be converted to a nearest date, 106 // For example, 2006-12-00 00:00:00 would become 2015-11-30 23:59:59. 107 tm := gotime.Date(t.Year(), gotime.Month(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Microsecond()*1000, loc) 108 year, month, day := tm.Date() 109 hour, minute, second := tm.Clock() 110 microsec := tm.Nanosecond() / 1000 111 // This function will check the result, and return an error if it's not the same with the origin input. 112 if year != t.Year() || int(month) != t.Month() || day != t.Day() || 113 hour != t.Hour() || minute != t.Minute() || second != t.Second() || 114 microsec != t.Microsecond() { 115 return tm, errors.Trace(ErrInvalidTimeFormat.GenWithStackByArgs(t)) 116 } 117 return tm, nil 118 } 119 120 // IsLeapYear returns if it's leap year. 121 func (t MysqlTime) IsLeapYear() bool { 122 return isLeapYear(t.year) 123 } 124 125 func isLeapYear(year uint16) bool { 126 return (year%4 == 0 && year%100 != 0) || year%400 == 0 127 } 128 129 var daysByMonth = [12]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} 130 131 // GetLastDay returns the last day of the month 132 func GetLastDay(year, month int) int { 133 var day = 0 134 if month > 0 && month <= 12 { 135 day = daysByMonth[month-1] 136 } 137 if month == 2 && isLeapYear(uint16(year)) { 138 day = 29 139 } 140 return day 141 } 142 143 func getFixDays(year, month, day int, ot gotime.Time) int { 144 if (year != 0 || month != 0) && day == 0 { 145 od := ot.Day() 146 t := ot.AddDate(year, month, day) 147 td := t.Day() 148 if od != td { 149 tm := int(t.Month()) - 1 150 tMax := GetLastDay(t.Year(), tm) 151 dd := tMax - od 152 return dd 153 } 154 } 155 return 0 156 } 157 158 // AddDate fix gap between mysql and golang api 159 // When we execute select date_add('2018-01-31',interval 1 month) in mysql we got 2018-02-28 160 // but in tidb we got 2018-03-03. 161 // Dig it and we found it's caused by golang api time.Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time , 162 // it says October 32 converts to November 1 ,it conflits with mysql. 163 // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add 164 func AddDate(year, month, day int64, ot gotime.Time) (nt gotime.Time) { 165 df := getFixDays(int(year), int(month), int(day), ot) 166 if df != 0 { 167 nt = ot.AddDate(int(year), int(month), df) 168 } else { 169 nt = ot.AddDate(int(year), int(month), int(day)) 170 } 171 return nt 172 } 173 174 func calcTimeFromSec(to *MysqlTime, seconds, microseconds int) { 175 to.hour = seconds / 3600 176 seconds = seconds % 3600 177 to.minute = uint8(seconds / 60) 178 to.second = uint8(seconds % 60) 179 to.microsecond = uint32(microseconds) 180 } 181 182 const secondsIn24Hour = 86400 183 184 // calcTimeDiff calculates difference between two datetime values as seconds + microseconds. 185 // t1 and t2 should be TIME/DATE/DATETIME value. 186 // sign can be +1 or -1, and t2 is preprocessed with sign first. 187 func calcTimeDiff(t1, t2 MysqlTime, sign int) (seconds, microseconds int, neg bool) { 188 days := calcDaynr(t1.Year(), t1.Month(), t1.Day()) 189 days2 := calcDaynr(t2.Year(), t2.Month(), t2.Day()) 190 days -= sign * days2 191 192 tmp := (int64(days)*secondsIn24Hour+ 193 int64(t1.Hour())*3600+int64(t1.Minute())*60+ 194 int64(t1.Second())- 195 int64(sign)*(int64(t2.Hour())*3600+int64(t2.Minute())*60+ 196 int64(t2.Second())))* 197 1e6 + 198 int64(t1.Microsecond()) - int64(sign)*int64(t2.Microsecond()) 199 200 if tmp < 0 { 201 tmp = -tmp 202 neg = true 203 } 204 seconds = int(tmp / 1e6) 205 microseconds = int(tmp % 1e6) 206 return 207 } 208 209 // datetimeToUint64 converts time value to integer in YYYYMMDDHHMMSS format. 210 func datetimeToUint64(t MysqlTime) uint64 { 211 return dateToUint64(t)*1e6 + timeToUint64(t) 212 } 213 214 // dateToUint64 converts time value to integer in YYYYMMDD format. 215 func dateToUint64(t MysqlTime) uint64 { 216 return uint64(t.Year())*10000 + 217 uint64(t.Month())*100 + 218 uint64(t.Day()) 219 } 220 221 // timeToUint64 converts time value to integer in HHMMSS format. 222 func timeToUint64(t MysqlTime) uint64 { 223 return uint64(t.Hour())*10000 + 224 uint64(t.Minute())*100 + 225 uint64(t.Second()) 226 } 227 228 // calcDaynr calculates days since 0000-00-00. 229 func calcDaynr(year, month, day int) int { 230 if year == 0 && month == 0 { 231 return 0 232 } 233 234 delsum := 365*year + 31*(month-1) + day 235 if month <= 2 { 236 year-- 237 } else { 238 delsum -= (month*4 + 23) / 10 239 } 240 temp := ((year/100 + 1) * 3) / 4 241 return delsum + year/4 - temp 242 } 243 244 // DateDiff calculates number of days between two days. 245 func DateDiff(startTime, endTime MysqlTime) int { 246 return calcDaynr(startTime.Year(), startTime.Month(), startTime.Day()) - calcDaynr(endTime.Year(), endTime.Month(), endTime.Day()) 247 } 248 249 // calcDaysInYear calculates days in one year, it works with 0 <= year <= 99. 250 func calcDaysInYear(year int) int { 251 if (year&3) == 0 && (year%100 != 0 || (year%400 == 0 && (year != 0))) { 252 return 366 253 } 254 return 365 255 } 256 257 // calcWeekday calculates weekday from daynr, returns 0 for Monday, 1 for Tuesday ... 258 func calcWeekday(daynr int, sundayFirstDayOfWeek bool) int { 259 daynr += 5 260 if sundayFirstDayOfWeek { 261 daynr++ 262 } 263 return daynr % 7 264 } 265 266 type weekBehaviour uint 267 268 const ( 269 // weekBehaviourMondayFirst set Monday as first day of week; otherwise Sunday is first day of week 270 weekBehaviourMondayFirst weekBehaviour = 1 << iota 271 // If set, Week is in range 1-53, otherwise Week is in range 0-53. 272 // Note that this flag is only relevant if WEEK_JANUARY is not set. 273 weekBehaviourYear 274 // If not set, Weeks are numbered according to ISO 8601:1988. 275 // If set, the week that contains the first 'first-day-of-week' is week 1. 276 weekBehaviourFirstWeekday 277 ) 278 279 func (v weekBehaviour) test(flag weekBehaviour) bool { 280 return (v & flag) != 0 281 } 282 283 func weekMode(mode int) weekBehaviour { 284 weekFormat := weekBehaviour(mode & 7) 285 if (weekFormat & weekBehaviourMondayFirst) == 0 { 286 weekFormat ^= weekBehaviourFirstWeekday 287 } 288 return weekFormat 289 } 290 291 // calcWeek calculates week and year for the time. 292 func calcWeek(t *MysqlTime, wb weekBehaviour) (year int, week int) { 293 var days int 294 daynr := calcDaynr(int(t.year), int(t.month), int(t.day)) 295 firstDaynr := calcDaynr(int(t.year), 1, 1) 296 mondayFirst := wb.test(weekBehaviourMondayFirst) 297 weekYear := wb.test(weekBehaviourYear) 298 firstWeekday := wb.test(weekBehaviourFirstWeekday) 299 300 weekday := calcWeekday(firstDaynr, !mondayFirst) 301 302 year = int(t.year) 303 304 if t.month == 1 && int(t.day) <= 7-weekday { 305 if !weekYear && 306 ((firstWeekday && weekday != 0) || (!firstWeekday && weekday >= 4)) { 307 week = 0 308 return 309 } 310 weekYear = true 311 year-- 312 days = calcDaysInYear(year) 313 firstDaynr -= days 314 weekday = (weekday + 53*7 - days) % 7 315 } 316 317 if (firstWeekday && weekday != 0) || 318 (!firstWeekday && weekday >= 4) { 319 days = daynr - (firstDaynr + 7 - weekday) 320 } else { 321 days = daynr - (firstDaynr - weekday) 322 } 323 324 if weekYear && days >= 52*7 { 325 weekday = (weekday + calcDaysInYear(year)) % 7 326 if (!firstWeekday && weekday < 4) || 327 (firstWeekday && weekday == 0) { 328 year++ 329 week = 1 330 return 331 } 332 } 333 week = days/7 + 1 334 return 335 } 336 337 // mixDateAndTime mixes a date value and a time value. 338 func mixDateAndTime(date, time *MysqlTime, neg bool) { 339 if !neg && time.hour < 24 { 340 date.hour = time.hour 341 date.minute = time.minute 342 date.second = time.second 343 date.microsecond = time.microsecond 344 return 345 } 346 347 // Time is negative or outside of 24 hours internal. 348 sign := -1 349 if neg { 350 sign = 1 351 } 352 seconds, microseconds, _ := calcTimeDiff(*date, *time, sign) 353 354 // If we want to use this function with arbitrary dates, this code will need 355 // to cover cases when time is negative and "date < -time". 356 357 days := seconds / secondsIn24Hour 358 calcTimeFromSec(date, seconds%secondsIn24Hour, microseconds) 359 year, month, day := getDateFromDaynr(uint(days)) 360 date.year = uint16(year) 361 date.month = uint8(month) 362 date.day = uint8(day) 363 } 364 365 var daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} 366 367 // getDateFromDaynr changes a daynr to year, month and day, 368 // daynr 0 is returned as date 00.00.00 369 func getDateFromDaynr(daynr uint) (year uint, month uint, day uint) { 370 if daynr <= 365 || daynr >= 3652500 { 371 return 372 } 373 374 year = daynr * 100 / 36525 375 temp := (((year-1)/100 + 1) * 3) / 4 376 dayOfYear := daynr - year*365 - (year-1)/4 + temp 377 378 daysInYear := calcDaysInYear(int(year)) 379 for dayOfYear > uint(daysInYear) { 380 dayOfYear -= uint(daysInYear) 381 year++ 382 daysInYear = calcDaysInYear(int(year)) 383 } 384 385 leapDay := uint(0) 386 if daysInYear == 366 { 387 if dayOfYear > 31+28 { 388 dayOfYear-- 389 if dayOfYear == 31+28 { 390 // Handle leapyears leapday. 391 leapDay = 1 392 } 393 } 394 } 395 396 month = 1 397 for _, days := range daysInMonth { 398 if dayOfYear <= uint(days) { 399 break 400 } 401 dayOfYear -= uint(days) 402 month++ 403 } 404 405 day = dayOfYear + leapDay 406 return 407 } 408 409 const ( 410 intervalYEAR = "YEAR" 411 intervalQUARTER = "QUARTER" 412 intervalMONTH = "MONTH" 413 intervalWEEK = "WEEK" 414 intervalDAY = "DAY" 415 intervalHOUR = "HOUR" 416 intervalMINUTE = "MINUTE" 417 intervalSECOND = "SECOND" 418 intervalMICROSECOND = "MICROSECOND" 419 ) 420 421 func timestampDiff(intervalType string, t1 MysqlTime, t2 MysqlTime) int64 { 422 seconds, microseconds, neg := calcTimeDiff(t2, t1, 1) 423 months := uint(0) 424 if intervalType == intervalYEAR || intervalType == intervalQUARTER || 425 intervalType == intervalMONTH { 426 var ( 427 yearBeg, yearEnd, monthBeg, monthEnd, dayBeg, dayEnd uint 428 secondBeg, secondEnd, microsecondBeg, microsecondEnd uint 429 ) 430 431 if neg { 432 yearBeg = uint(t2.Year()) 433 yearEnd = uint(t1.Year()) 434 monthBeg = uint(t2.Month()) 435 monthEnd = uint(t1.Month()) 436 dayBeg = uint(t2.Day()) 437 dayEnd = uint(t1.Day()) 438 secondBeg = uint(t2.Hour()*3600 + t2.Minute()*60 + t2.Second()) 439 secondEnd = uint(t1.Hour()*3600 + t1.Minute()*60 + t1.Second()) 440 microsecondBeg = uint(t2.Microsecond()) 441 microsecondEnd = uint(t1.Microsecond()) 442 } else { 443 yearBeg = uint(t1.Year()) 444 yearEnd = uint(t2.Year()) 445 monthBeg = uint(t1.Month()) 446 monthEnd = uint(t2.Month()) 447 dayBeg = uint(t1.Day()) 448 dayEnd = uint(t2.Day()) 449 secondBeg = uint(t1.Hour()*3600 + t1.Minute()*60 + t1.Second()) 450 secondEnd = uint(t2.Hour()*3600 + t2.Minute()*60 + t2.Second()) 451 microsecondBeg = uint(t1.Microsecond()) 452 microsecondEnd = uint(t2.Microsecond()) 453 } 454 455 // calc years 456 years := yearEnd - yearBeg 457 if monthEnd < monthBeg || 458 (monthEnd == monthBeg && dayEnd < dayBeg) { 459 years-- 460 } 461 462 // calc months 463 months = 12 * years 464 if monthEnd < monthBeg || 465 (monthEnd == monthBeg && dayEnd < dayBeg) { 466 months += 12 - (monthBeg - monthEnd) 467 } else { 468 months += monthEnd - monthBeg 469 } 470 471 if dayEnd < dayBeg { 472 months-- 473 } else if (dayEnd == dayBeg) && 474 ((secondEnd < secondBeg) || 475 (secondEnd == secondBeg && microsecondEnd < microsecondBeg)) { 476 months-- 477 } 478 } 479 480 negV := int64(1) 481 if neg { 482 negV = -1 483 } 484 switch intervalType { 485 case intervalYEAR: 486 return int64(months) / 12 * negV 487 case intervalQUARTER: 488 return int64(months) / 3 * negV 489 case intervalMONTH: 490 return int64(months) * negV 491 case intervalWEEK: 492 return int64(seconds) / secondsIn24Hour / 7 * negV 493 case intervalDAY: 494 return int64(seconds) / secondsIn24Hour * negV 495 case intervalHOUR: 496 return int64(seconds) / 3600 * negV 497 case intervalMINUTE: 498 return int64(seconds) / 60 * negV 499 case intervalSECOND: 500 return int64(seconds) * negV 501 case intervalMICROSECOND: 502 // In MySQL difference between any two valid datetime values 503 // in microseconds fits into longlong. 504 return int64(seconds*1000000+microseconds) * negV 505 } 506 507 return 0 508 }