github.com/matrixorigin/matrixone@v0.7.0/pkg/container/types/datetime.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  	"fmt"
    19  	"math"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    25  )
    26  
    27  const (
    28  	secsPerMinute       = 60
    29  	secsPerHour         = 60 * secsPerMinute
    30  	secsPerDay          = 24 * secsPerHour
    31  	secsPerWeek         = 7 * secsPerDay
    32  	NanoSecsPerSec      = 1000000000 // 10^9
    33  	microSecsPerSec     = 1000000    // 10^6
    34  	MillisecsPerSec     = 1000       // 10^3
    35  	nanoSecsPerMicroSec = 1000
    36  	microSecsPerDay     = secsPerDay * microSecsPerSec
    37  	MaxDatetimeYear     = 9999
    38  	MinDatetimeYear     = 1
    39  
    40  	minHourInDay, maxHourInDay           = 0, 23
    41  	minMinuteInHour, maxMinuteInHour     = 0, 59
    42  	minSecondInMinute, maxSecondInMinute = 0, 59
    43  )
    44  
    45  var (
    46  	precisionVal = []Datetime{1000000, 100000, 10000, 1000, 100, 10, 1}
    47  )
    48  
    49  // The Datetime type holds number of microseconds since January 1, year 1 in Gregorian calendar
    50  
    51  func (dt Datetime) String() string {
    52  	y, m, d, _ := dt.ToDate().Calendar(true)
    53  	hour, minute, sec := dt.Clock()
    54  	return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", y, m, d, hour, minute, sec)
    55  }
    56  
    57  func (dt Datetime) String2(precision int32) string {
    58  	y, m, d, _ := dt.ToDate().Calendar(true)
    59  	hour, minute, sec := dt.Clock()
    60  	if precision > 0 {
    61  		msec := int64(dt) % microSecsPerSec
    62  		msecInstr := fmt.Sprintf("%06d\n", msec)
    63  		msecInstr = msecInstr[:precision]
    64  
    65  		return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d"+"."+msecInstr, y, m, d, hour, minute, sec)
    66  	}
    67  	return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", y, m, d, hour, minute, sec)
    68  }
    69  
    70  // ParseDatetime will parse a string to be a Datetime
    71  // Support Format:
    72  // 1. all the Date value
    73  // 2. yyyy-mm-dd hh:mm:ss(.msec)
    74  // now support mm/dd/hh/mm/ss can be single number
    75  // 3. yyyymmddhhmmss(.msec)
    76  // during parsing, the Datetime value will be rounded(away from zero) to the predefined precision, for example:
    77  // Datetime(3) input string   					parsing result
    78  //
    79  //	"1999-09-09 11:11:11.1234"		"1999-09-09 11:11:11.123"
    80  //	"1999-09-09 11:11:11.1235"		"1999-09-09 11:11:11.124"
    81  //	"1999-09-09 11:11:11.9994"      "1999-09-09 11:11:11.999"
    82  //	"1999-09-09 11:11:11.9995"      "1999-09-09 11:11:12.000"
    83  func ParseDatetime(s string, precision int32) (Datetime, error) {
    84  	s = strings.TrimSpace(s)
    85  	if len(s) < 14 {
    86  		if d, err := ParseDateCast(s); err == nil {
    87  			return d.ToDatetime(), nil
    88  		}
    89  		return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
    90  	}
    91  	var year int32
    92  	var month, day, hour, minute, second uint8
    93  	var msec uint32 = 0
    94  	var carry uint32 = 0
    95  	var err error
    96  
    97  	if s[4] == '-' || s[4] == '/' {
    98  		var num int64
    99  		var unum uint64
   100  		strArr := strings.Split(s, " ")
   101  		if len(strArr) != 2 {
   102  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   103  		}
   104  		// solve year/month/day
   105  		front := strings.Split(strArr[0], s[4:5])
   106  		if len(front) != 3 {
   107  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   108  		}
   109  		num, err = strconv.ParseInt(front[0], 10, 32)
   110  		if err != nil {
   111  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   112  		}
   113  		year = int32(num)
   114  		unum, err = strconv.ParseUint(front[1], 10, 8)
   115  		if err != nil {
   116  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   117  		}
   118  		month = uint8(unum)
   119  		unum, err = strconv.ParseUint(front[2], 10, 8)
   120  		if err != nil {
   121  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   122  		}
   123  		day = uint8(unum)
   124  
   125  		if !ValidDate(year, month, day) {
   126  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   127  		}
   128  
   129  		middleAndBack := strings.Split(strArr[1], ".")
   130  		// solve hour/minute/second
   131  		middle := strings.Split(middleAndBack[0], ":")
   132  		if len(middle) != 3 {
   133  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   134  		}
   135  		unum, err = strconv.ParseUint(middle[0], 10, 8)
   136  		if err != nil {
   137  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   138  		}
   139  		hour = uint8(unum)
   140  		unum, err = strconv.ParseUint(middle[1], 10, 8)
   141  		if err != nil {
   142  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   143  		}
   144  		minute = uint8(unum)
   145  		unum, err = strconv.ParseUint(middle[2], 10, 8)
   146  		if err != nil {
   147  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   148  		}
   149  		second = uint8(unum)
   150  		if !ValidTimeInDay(hour, minute, second) {
   151  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   152  		}
   153  		// solve microsecond
   154  		if len(middleAndBack) == 2 {
   155  			msec, carry, err = getMsec(middleAndBack[1], precision)
   156  			if err != nil {
   157  				return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   158  			}
   159  		} else if len(middleAndBack) > 2 {
   160  			return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   161  		}
   162  	} else {
   163  		year = int32(s[0]-'0')*1000 + int32(s[1]-'0')*100 + int32(s[2]-'0')*10 + int32(s[3]-'0')
   164  		month = (s[4]-'0')*10 + (s[5] - '0')
   165  		day = (s[6]-'0')*10 + (s[7] - '0')
   166  		hour = (s[8]-'0')*10 + (s[9] - '0')
   167  		minute = (s[10]-'0')*10 + (s[11] - '0')
   168  		second = (s[12]-'0')*10 + (s[13] - '0')
   169  		if len(s) > 14 {
   170  			if len(s) > 15 && s[14] == '.' {
   171  				msecStr := s[15:]
   172  				msec, carry, err = getMsec(msecStr, precision)
   173  				if err != nil {
   174  					return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   175  				}
   176  			} else {
   177  				return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   178  			}
   179  		}
   180  	}
   181  	if !ValidDate(year, month, day) {
   182  		return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   183  	}
   184  	result := DatetimeFromClock(year, month, day, hour, minute, second+uint8(carry), msec)
   185  	y, m, d, _ := result.ToDate().Calendar(true)
   186  	if !ValidDate(y, m, d) {
   187  		return -1, moerr.NewInvalidInputNoCtx("invalid datatime value %s", s)
   188  	}
   189  	return result, nil
   190  }
   191  
   192  // validTimeInDay return true if hour, minute and second can be a time during a day
   193  func ValidTimeInDay(h, m, s uint8) bool {
   194  	if h < minHourInDay || h > maxHourInDay {
   195  		return false
   196  	}
   197  	if m < minMinuteInHour || m > maxMinuteInHour {
   198  		return false
   199  	}
   200  	if s < minSecondInMinute || s > maxSecondInMinute {
   201  		return false
   202  	}
   203  	return true
   204  }
   205  
   206  func (dt Datetime) UnixTimestamp(loc *time.Location) int64 {
   207  	return dt.ConvertToGoTime(loc).Unix()
   208  }
   209  
   210  func DatetimeFromUnix(loc *time.Location, ts int64) Datetime {
   211  	t := time.Unix(ts, 0).In(loc)
   212  	_, offset := t.Zone()
   213  	return Datetime((ts+int64(offset))*microSecsPerSec + unixEpochSecs)
   214  }
   215  
   216  func DatetimeFromUnixWithNsec(loc *time.Location, sec int64, nsec int64) Datetime {
   217  	t := time.Unix(sec, nsec).In(loc)
   218  	_, offset := t.Zone()
   219  	msec := math.Round(float64(nsec) / 1000)
   220  	return Datetime((sec+int64(offset))*microSecsPerSec + int64(msec) + unixEpochSecs)
   221  }
   222  
   223  func Now(loc *time.Location) Datetime {
   224  	now := time.Now().In(loc)
   225  	_, offset := now.Zone()
   226  	return Datetime(now.UnixMicro() + int64(offset)*microSecsPerSec + unixEpochSecs)
   227  }
   228  
   229  func UTC() Datetime {
   230  	return Datetime(time.Now().UnixMicro() + unixEpochSecs)
   231  }
   232  
   233  func (dt Datetime) ToDate() Date {
   234  	return Date((dt.sec()) / secsPerDay)
   235  }
   236  
   237  // We need to truncate the part after precision position when cast
   238  // between different precision.
   239  func (dt Datetime) ToTime(precision int32) Time {
   240  	if precision == 6 {
   241  		return Time(dt % microSecsPerDay)
   242  	}
   243  
   244  	// truncate the date part
   245  	ms := dt % microSecsPerDay
   246  
   247  	base := ms / precisionVal[precision]
   248  	if ms%precisionVal[precision]/precisionVal[precision+1] >= 5 { // check carry
   249  		base += 1
   250  	}
   251  
   252  	return Time(base * precisionVal[precision])
   253  }
   254  
   255  func (dt Datetime) Clock() (hour, minute, sec int8) {
   256  	t := (dt.sec()) % secsPerDay
   257  	hour = int8(t / secsPerHour)
   258  	minute = int8(t % secsPerHour / secsPerMinute)
   259  	sec = int8(t % secsPerMinute)
   260  	return
   261  }
   262  
   263  func (dt Datetime) Sec() int8 {
   264  	_, _, sec := dt.Clock()
   265  	return sec
   266  }
   267  
   268  func (dt Datetime) Minute() int8 {
   269  	_, minute, _ := dt.Clock()
   270  	return minute
   271  }
   272  
   273  func (dt Datetime) Hour() int8 {
   274  	hour, _, _ := dt.Clock()
   275  	return hour
   276  }
   277  
   278  func DatetimeFromClock(year int32, month, day, hour, minute, sec uint8, msec uint32) Datetime {
   279  	days := DateFromCalendar(year, month, day)
   280  	secs := int64(days)*secsPerDay + int64(hour)*secsPerHour + int64(minute)*secsPerMinute + int64(sec)
   281  	return Datetime(secs*microSecsPerSec + int64(msec))
   282  }
   283  
   284  func (dt Datetime) ConvertToGoTime(loc *time.Location) time.Time {
   285  	year, mon, day, _ := dt.ToDate().Calendar(true)
   286  	hour, minute, sec := dt.Clock()
   287  	nsec := dt.MicroSec() * 1000
   288  	return time.Date(int(year), time.Month(mon), int(day), int(hour), int(minute), int(sec), int(nsec), loc)
   289  }
   290  
   291  func (dt Datetime) AddDateTime(addMonth, addYear int64, timeType TimeType) (Datetime, bool) {
   292  	// corner case: mysql: date_add('2022-01-31',interval 1 month) -> 2022-02-28
   293  	// only in the month year year-month
   294  	oldDate := dt.ToDate()
   295  	y, m, d, _ := oldDate.Calendar(true)
   296  	year := int64(y) + addYear + addMonth/12
   297  	month := int64(m) + addMonth%12
   298  	if month <= 0 {
   299  		year--
   300  		month += 12
   301  	}
   302  	if month > 12 {
   303  		year++
   304  		month -= 12
   305  	}
   306  
   307  	y = int32(year)
   308  	m = uint8(month)
   309  
   310  	lastDay := LastDay(y, m)
   311  	if lastDay < d {
   312  		d = lastDay
   313  	}
   314  
   315  	switch timeType {
   316  	case DateType:
   317  		if !ValidDate(y, m, d) {
   318  			return 0, false
   319  		}
   320  	case DateTimeType, TimeStampType:
   321  		if !ValidDatetime(y, m, d) {
   322  			return 0, false
   323  		}
   324  	}
   325  	newDate := DateFromCalendar(y, m, d)
   326  	return dt + Datetime(newDate-oldDate)*secsPerDay*microSecsPerSec, true
   327  }
   328  
   329  // AddInterval now date or datetime use the function to add/sub date,
   330  // we need a bool arg to tell isDate/isDatetime
   331  // date/datetime have different regions, so we don't use same valid function
   332  // return type bool means the if the date/datetime is valid
   333  func (dt Datetime) AddInterval(nums int64, its IntervalType, timeType TimeType) (Datetime, bool) {
   334  	var addMonth, addYear int64
   335  	switch its {
   336  	case Second:
   337  		nums *= microSecsPerSec
   338  	case Minute:
   339  		nums *= microSecsPerSec * secsPerMinute
   340  	case Hour:
   341  		nums *= microSecsPerSec * secsPerHour
   342  	case Day:
   343  		nums *= microSecsPerSec * secsPerDay
   344  	case Week:
   345  		nums *= microSecsPerSec * secsPerWeek
   346  	case Month:
   347  		addMonth = nums
   348  		return dt.AddDateTime(addMonth, addYear, timeType)
   349  	case Quarter:
   350  		addMonth = 3 * nums
   351  		return dt.AddDateTime(addMonth, addYear, timeType)
   352  	case Year:
   353  		addYear = nums
   354  		return dt.AddDateTime(addMonth, addYear, timeType)
   355  	}
   356  
   357  	newDate := dt + Datetime(nums)
   358  	y, m, d, _ := newDate.ToDate().Calendar(true)
   359  	if !ValidDatetime(y, m, d) {
   360  		return 0, false
   361  	}
   362  	return newDate, true
   363  }
   364  
   365  func (dt Datetime) DateTimeDiffWithUnit(its string, secondDt Datetime) (int64, error) {
   366  	switch its {
   367  	case "microsecond":
   368  		return int64(dt - secondDt), nil
   369  	case "second":
   370  		return (dt - secondDt).sec(), nil
   371  	case "minute":
   372  		return int64(dt-secondDt) / (microSecsPerSec * secsPerMinute), nil
   373  	case "hour":
   374  		return int64(dt-secondDt) / (microSecsPerSec * secsPerHour), nil
   375  	case "day":
   376  		return int64(dt-secondDt) / (microSecsPerSec * secsPerDay), nil
   377  	case "week":
   378  		return int64(dt-secondDt) / (microSecsPerSec * secsPerWeek), nil
   379  	case "month":
   380  		return dt.ConvertToMonth(secondDt), nil
   381  	case "quarter":
   382  		return dt.ConvertToMonth(secondDt) / 3, nil
   383  	case "year":
   384  		return dt.ConvertToMonth(secondDt) / 12, nil
   385  	}
   386  	return 0, moerr.NewInvalidInputNoCtx("invalid time_stamp_unit input")
   387  }
   388  
   389  func (dt Datetime) DatetimeMinusWithSecond(secondDt Datetime) int64 {
   390  	return int64((dt - secondDt) / microSecsPerSec)
   391  }
   392  
   393  func (dt Datetime) ConvertToMonth(secondDt Datetime) int64 {
   394  
   395  	dayDiff := int64(dt.ToDate().Day()) - int64(secondDt.ToDate().Day())
   396  	monthDiff := (int64(dt.ToDate().Year())-int64(secondDt.ToDate().Year()))*12 + int64(dt.ToDate().Month()) - int64(secondDt.ToDate().Month())
   397  
   398  	if dayDiff >= 0 {
   399  		return monthDiff
   400  	} else {
   401  		return monthDiff - 1
   402  	}
   403  }
   404  
   405  func (dt Datetime) MicroSec() int64 {
   406  	return int64(dt) % microSecsPerSec
   407  }
   408  
   409  func (dt Datetime) sec() int64 {
   410  	return int64(dt) / microSecsPerSec
   411  }
   412  
   413  func (dt Datetime) Year() uint16 {
   414  	return dt.ToDate().Year()
   415  }
   416  
   417  func (dt Datetime) Month() uint8 {
   418  	return dt.ToDate().Month()
   419  }
   420  
   421  func (dt Datetime) Day() uint8 {
   422  	return dt.ToDate().Day()
   423  }
   424  
   425  func (dt Datetime) WeekOfYear() (int32, uint8) {
   426  	return dt.ToDate().WeekOfYear()
   427  }
   428  
   429  func (dt Datetime) DayOfYear() uint16 {
   430  	return dt.ToDate().DayOfYear()
   431  }
   432  
   433  func (dt Datetime) DayOfWeek() Weekday {
   434  	return dt.ToDate().DayOfWeek()
   435  }
   436  
   437  func (dt Datetime) Week(mode int) int {
   438  	return dt.ToDate().Week(mode)
   439  }
   440  
   441  // YearWeek returns year and week.
   442  func (dt Datetime) YearWeek(mode int) (year int, week int) {
   443  	return dt.ToDate().YearWeek(mode)
   444  }
   445  
   446  func (dt Datetime) ToTimestamp(loc *time.Location) Timestamp {
   447  	return Timestamp(dt.ConvertToGoTime(loc).UnixMicro() + unixEpochSecs)
   448  }
   449  
   450  func (dt Datetime) SecondMicrosecondStr() string {
   451  	result := fmt.Sprintf("%02d", dt.Sec()) + "." + fmt.Sprintf("%06d", dt.MicroSec())
   452  	return result
   453  }
   454  
   455  func (dt Datetime) MinuteMicrosecondStr() string {
   456  	result := fmt.Sprintf("%02d", dt.Minute()) + ":" + fmt.Sprintf("%02d", dt.Sec()) + "." + fmt.Sprintf("%06d", dt.MicroSec())
   457  	return result
   458  }
   459  
   460  func (dt Datetime) MinuteSecondStr() string {
   461  	result := fmt.Sprintf("%02d", dt.Minute()) + ":" + fmt.Sprintf("%02d", dt.Sec())
   462  	return result
   463  }
   464  
   465  func (dt Datetime) HourMicrosecondStr() string {
   466  	result := fmt.Sprintf("%2d", dt.Hour()) + ":" + fmt.Sprintf("%02d", dt.Minute()) + ":" + fmt.Sprintf("%02d", dt.Sec()) + "." + fmt.Sprintf("%06d", dt.MicroSec())
   467  	return result
   468  }
   469  
   470  func (dt Datetime) HourSecondStr() string {
   471  	result := fmt.Sprintf("%2d", dt.Hour()) + ":" + fmt.Sprintf("%02d", dt.Minute()) + ":" + fmt.Sprintf("%02d", dt.Sec())
   472  	return result
   473  }
   474  
   475  func (dt Datetime) HourMinuteStr() string {
   476  	result := fmt.Sprintf("%2d", dt.Hour()) + ":" + fmt.Sprintf("%02d", dt.Minute())
   477  	return result
   478  }
   479  
   480  func (dt Datetime) DayMicrosecondStr() string {
   481  	result := fmt.Sprintf("%02d", dt.Day()) + " " + dt.HourMicrosecondStr()
   482  	return result
   483  }
   484  
   485  func (dt Datetime) DaySecondStr() string {
   486  	result := fmt.Sprintf("%02d", dt.Day()) + " " + dt.HourSecondStr()
   487  	return result
   488  }
   489  
   490  func (dt Datetime) DayMinuteStr() string {
   491  	result := fmt.Sprintf("%02d", dt.Day()) + " " + dt.HourMinuteStr()
   492  	return result
   493  }
   494  
   495  func (dt Datetime) DayHourStr() string {
   496  	result := fmt.Sprintf("%02d", dt.Day()) + " " + fmt.Sprintf("%02d", dt.Hour())
   497  	return result
   498  }
   499  
   500  func (dt Datetime) YearMonthStr() string {
   501  	result := fmt.Sprintf("%04d", dt.Year()) + " " + fmt.Sprintf("%02d", dt.Month())
   502  	return result
   503  }
   504  
   505  // date[0001-01-01 00:00:00 to 9999-12-31 23:59:59]
   506  func ValidDatetime(year int32, month, day uint8) bool {
   507  	if year >= MinDatetimeYear && year <= MaxDatetimeYear {
   508  		if MinMonthInYear <= month && month <= MaxMonthInYear {
   509  			if day > 0 {
   510  				if isLeap(year) {
   511  					return day <= leapYearMonthDays[month-1]
   512  				} else {
   513  					return day <= flatYearMonthDays[month-1]
   514  				}
   515  			}
   516  		}
   517  	}
   518  	return false
   519  }
   520  
   521  func (dt Datetime) SecsSinceUnixEpoch() int64 {
   522  	return (int64(dt) - unixEpochSecs) / microSecsPerSec
   523  }