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  }