|
- // Copyright 2015 PingCAP, Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package mysql
-
- import (
- "bytes"
- "fmt"
- "math"
- "strconv"
- "strings"
- "time"
- "unicode"
-
- "github.com/juju/errors"
- )
-
- // Portable analogs of some common call errors.
- var (
- ErrInvalidTimeFormat = errors.New("invalid time format")
- ErrInvalidYearFormat = errors.New("invalid year format")
- ErrInvalidYear = errors.New("invalid year")
- )
-
- // Time format without fractional seconds precision.
- const (
- DateFormat = "2006-01-02"
- TimeFormat = "2006-01-02 15:04:05"
- // TimeFSPFormat is time format with fractional seconds precision.
- TimeFSPFormat = "2006-01-02 15:04:05.000000"
- )
-
- const (
- // MinYear is the minimum for mysql year type.
- MinYear int16 = 1901
- // MaxYear is the maximum for mysql year type.
- MaxYear int16 = 2155
-
- // MinTime is the minimum for mysql time type.
- MinTime = -time.Duration(838*3600+59*60+59) * time.Second
- // MaxTime is the maximum for mysql time type.
- MaxTime = time.Duration(838*3600+59*60+59) * time.Second
-
- zeroDatetimeStr = "0000-00-00 00:00:00"
- zeroDateStr = "0000-00-00"
- )
-
- // Zero values for different types.
- var (
- // ZeroDuration is the zero value for Duration type.
- ZeroDuration = Duration{Duration: time.Duration(0), Fsp: DefaultFsp}
-
- // ZeroTime is the zero value for time.Time type.
- ZeroTime = time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC)
-
- // ZeroDatetime is the zero value for datetime Time.
- ZeroDatetime = Time{
- Time: ZeroTime,
- Type: TypeDatetime,
- Fsp: DefaultFsp,
- }
-
- // ZeroTimestamp is the zero value for timestamp Time.
- ZeroTimestamp = Time{
- Time: ZeroTime,
- Type: TypeTimestamp,
- Fsp: DefaultFsp,
- }
-
- // ZeroDate is the zero value for date Time.
- ZeroDate = Time{
- Time: ZeroTime,
- Type: TypeDate,
- Fsp: DefaultFsp,
- }
- )
-
- var (
- // MinDatetime is the minimum for mysql datetime type.
- MinDatetime = time.Date(1000, 1, 1, 0, 0, 0, 0, time.Local)
- // MaxDatetime is the maximum for mysql datetime type.
- MaxDatetime = time.Date(9999, 12, 31, 23, 59, 59, 999999, time.Local)
-
- // MinTimestamp is the minimum for mysql timestamp type.
- MinTimestamp = time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC)
- // MaxTimestamp is the maximum for mysql timestamp type.
- MaxTimestamp = time.Date(2038, 1, 19, 3, 14, 7, 999999, time.UTC)
-
- // WeekdayNames lists names of weekdays, which are used in builtin time function `dayname`.
- WeekdayNames = []string{
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday",
- "Sunday",
- }
- )
-
- // Time is the struct for handling datetime, timestamp and date.
- // TODO: check if need a NewTime function to set Fsp default value?
- type Time struct {
- time.Time
- Type uint8
- // Fsp is short for Fractional Seconds Precision.
- // See http://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html
- Fsp int
- }
-
- // CurrentTime returns current time with type tp.
- func CurrentTime(tp uint8) Time {
- return Time{Time: time.Now(), Type: tp, Fsp: 0}
- }
-
- func (t Time) String() string {
- if t.IsZero() {
- if t.Type == TypeDate {
- return zeroDateStr
- }
-
- return zeroDatetimeStr
- }
-
- if t.Type == TypeDate {
- return t.Time.Format(DateFormat)
- }
-
- tfStr := TimeFormat
- if t.Fsp > 0 {
- tfStr = fmt.Sprintf("%s.%s", tfStr, strings.Repeat("0", t.Fsp))
- }
-
- return t.Time.Format(tfStr)
- }
-
- // IsZero returns a boolean indicating whether the time is equal to ZeroTime.
- func (t Time) IsZero() bool {
- return t.Time.Equal(ZeroTime)
- }
-
- // Marshal returns the binary encoding of time.
- func (t Time) Marshal() ([]byte, error) {
- var (
- b []byte
- err error
- )
-
- switch t.Type {
- case TypeDatetime, TypeDate:
- // We must use t's Zone not current Now Zone,
- // For EDT/EST, even we create the time with time.Local location,
- // we may still have a different zone with current Now time.
- _, offset := t.Zone()
- // For datetime and date type, we have a trick to marshal.
- // e.g, if local time is 2010-10-10T10:10:10 UTC+8
- // we will change this to 2010-10-10T10:10:10 UTC and then marshal.
- b, err = t.Time.Add(time.Duration(offset) * time.Second).UTC().MarshalBinary()
- case TypeTimestamp:
- b, err = t.Time.UTC().MarshalBinary()
- default:
- err = errors.Errorf("invalid time type %d", t.Type)
- }
-
- if err != nil {
- return nil, errors.Trace(err)
- }
-
- return b, nil
- }
-
- // Unmarshal decodes the binary data into Time with current local time.
- func (t *Time) Unmarshal(b []byte) error {
- return t.UnmarshalInLocation(b, time.Local)
- }
-
- // UnmarshalInLocation decodes the binary data
- // into Time with a specific time Location.
- func (t *Time) UnmarshalInLocation(b []byte, loc *time.Location) error {
- if err := t.Time.UnmarshalBinary(b); err != nil {
- return errors.Trace(err)
- }
-
- if t.IsZero() {
- return nil
- }
-
- if t.Type == TypeDatetime || t.Type == TypeDate {
- // e.g, for 2010-10-10T10:10:10 UTC, we will unmarshal to 2010-10-10T10:10:10 location
- _, offset := t.Time.In(loc).Zone()
-
- t.Time = t.Time.Add(-time.Duration(offset) * time.Second).In(loc)
- if t.Type == TypeDate {
- // for date type ,we will only use year, month and day.
- year, month, day := t.Time.Date()
- t.Time = time.Date(year, month, day, 0, 0, 0, 0, loc)
- }
- } else if t.Type == TypeTimestamp {
- t.Time = t.Time.In(loc)
- } else {
- return errors.Errorf("invalid time type %d", t.Type)
- }
-
- return nil
- }
-
- const numberFormat = "20060102150405"
-
- // ToNumber returns a formatted number.
- // e.g,
- // 2012-12-12T10:10:10 -> 20121212101010
- // 2012-12-12T10:10:10.123456 -> 20121212101010.123456
- func (t Time) ToNumber() Decimal {
- if t.IsZero() {
- return ZeroDecimal
- }
-
- tfStr := numberFormat
- if t.Fsp > 0 {
- tfStr = fmt.Sprintf("%s.%s", tfStr, strings.Repeat("0", t.Fsp))
- }
-
- s := t.Time.Format(tfStr)
- // We skip checking error here because time formatted string can be parsed certainly.
- d, _ := ParseDecimal(s)
- return d
- }
-
- // Convert converts t with type tp.
- func (t Time) Convert(tp uint8) (Time, error) {
- if t.Type == tp || t.IsZero() {
- return Time{Time: t.Time, Type: tp, Fsp: t.Fsp}, nil
- }
-
- switch tp {
- case TypeDatetime:
- return Time{Time: t.Time, Type: TypeDatetime, Fsp: t.Fsp}, nil
- case TypeTimestamp:
- nt := Time{Time: t.Time, Type: TypeTimestamp, Fsp: t.Fsp}
- if !checkTimestamp(nt) {
- return ZeroTimestamp, errors.Trace(ErrInvalidTimeFormat)
- }
- return nt, nil
- case TypeDate:
- year, month, day := t.Time.Date()
- return Time{Time: time.Date(year, month, day, 0, 0, 0, 0, time.Local),
- Type: TypeDate, Fsp: 0}, nil
- default:
- return Time{Time: ZeroTime, Type: tp}, errors.Errorf("invalid time type %d", tp)
- }
- }
-
- // ConvertToDuration converts mysql datetime, timestamp and date to mysql time type.
- // e.g,
- // 2012-12-12T10:10:10 -> 10:10:10
- // 2012-12-12 -> 0
- func (t Time) ConvertToDuration() (Duration, error) {
- if t.IsZero() {
- return ZeroDuration, nil
- }
-
- hour, minute, second := t.Clock()
- frac := t.Nanosecond()
-
- d := time.Duration(hour*3600+minute*60+second)*time.Second + time.Duration(frac)
-
- // TODO: check convert validation
- return Duration{Duration: time.Duration(d), Fsp: t.Fsp}, nil
- }
-
- // Compare returns an integer comparing the time instant t to o.
- // If t is after o, return 1, equal o, return 0, before o, return -1.
- func (t Time) Compare(o Time) int {
- if t.Time.After(o.Time) {
- return 1
- } else if t.Time.Equal(o.Time) {
- return 0
- } else {
- return -1
- }
- }
-
- // CompareString is like Compare,
- // but parses string to Time then compares.
- func (t Time) CompareString(str string) (int, error) {
- // use MaxFsp to parse the string
- o, err := ParseTime(str, t.Type, MaxFsp)
- if err != nil {
- return 0, errors.Trace(err)
- }
-
- return t.Compare(o), nil
- }
-
- // RoundFrac rounds fractional seconds precision with new fsp and returns a new one.
- // We will use the “round half up” rule, e.g, >= 0.5 -> 1, < 0.5 -> 0,
- // so 2011:11:11 10:10:10.888888 round 0 -> 2011:11:11 10:10:11
- // and 2011:11:11 10:10:10.111111 round 0 -> 2011:11:11 10:10:10
- func (t Time) RoundFrac(fsp int) (Time, error) {
- if t.Type == TypeDate {
- // date type has no fsp
- return t, nil
- }
-
- fsp, err := checkFsp(fsp)
- if err != nil {
- return t, errors.Trace(err)
- }
-
- if fsp == t.Fsp {
- // have same fsp
- return t, nil
- }
-
- nt := t.Time.Round(time.Duration(math.Pow10(9-fsp)) * time.Nanosecond)
- return Time{Time: nt, Type: t.Type, Fsp: fsp}, nil
- }
-
- func parseDateFormat(format string) []string {
- format = strings.TrimSpace(format)
-
- start := 0
- seps := []string{}
- for i := 0; i < len(format); i++ {
- // Date fromat must start and end with number.
- if i == 0 || i == len(format)-1 {
- if !unicode.IsNumber(rune(format[i])) {
- return nil
- }
-
- continue
- }
-
- // Seperator is a single none-number char.
- if !unicode.IsNumber(rune(format[i])) {
- if !unicode.IsNumber(rune(format[i-1])) {
- return nil
- }
-
- seps = append(seps, format[start:i])
- start = i + 1
- }
-
- }
-
- seps = append(seps, format[start:])
- return seps
- }
-
- func parseDatetime(str string, fsp int) (Time, error) {
- // Try to split str with delimiter.
- // TODO: only punctuation can be the delimiter for date parts or time parts.
- // But only space and T can be the delimiter between the date and time part.
- var (
- year int
- month int
- day int
- hour int
- minute int
- second int
- frac int
- fracStr string
-
- err error
- )
-
- seps := parseDateFormat(str)
-
- switch len(seps) {
- case 1:
- // No delimiter.
- if len(str) == 14 {
- // YYYYMMDDHHMMSS
- _, err = fmt.Sscanf(str, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second)
- } else if len(str) == 12 {
- // YYMMDDHHMMSS
- _, err = fmt.Sscanf(str, "%2d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second)
- year = adjustYear(year)
- } else if len(str) == 8 {
- // YYYYMMDD
- _, err = fmt.Sscanf(str, "%4d%2d%2d", &year, &month, &day)
- } else if len(str) == 6 {
- // YYMMDD
- _, err = fmt.Sscanf(str, "%2d%2d%2d", &year, &month, &day)
- year = adjustYear(year)
- } else {
- return ZeroDatetime, errors.Trace(ErrInvalidTimeFormat)
- }
- case 2:
- s := seps[0]
- fracStr = seps[1]
-
- if len(s) == 14 {
- // YYYYMMDDHHMMSS.fraction
- _, err = fmt.Sscanf(s, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second)
- } else if len(s) == 12 {
- // YYMMDDHHMMSS.fraction
- _, err = fmt.Sscanf(s, "%2d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second)
- year = adjustYear(year)
- } else {
- return ZeroDatetime, errors.Trace(ErrInvalidTimeFormat)
- }
- case 3:
- // YYYY-MM-DD
- err = scanTimeArgs(seps, &year, &month, &day)
- case 6:
- // We don't have fractional seconds part.
- // YYYY-MM-DD HH-MM-SS
- err = scanTimeArgs(seps, &year, &month, &day, &hour, &minute, &second)
- case 7:
- // We have fractional seconds part.
- // YYY-MM-DD HH-MM-SS.fraction
- err = scanTimeArgs(seps[0:len(seps)-1], &year, &month, &day, &hour, &minute, &second)
- fracStr = seps[len(seps)-1]
- default:
- return ZeroDatetime, errors.Trace(ErrInvalidTimeFormat)
- }
-
- if err != nil {
- return ZeroDatetime, errors.Trace(err)
- }
-
- // If str is sepereated by delimiters, the first one is year, and if the year is 2 digit,
- // we should adjust it.
- // TODO: ajust year is very complex, now we only consider the simplest way.
- if len(seps[0]) == 2 {
- year = adjustYear(year)
- }
-
- frac, err = parseFrac(fracStr, fsp)
- if err != nil {
- return ZeroDatetime, errors.Trace(err)
- }
-
- t, err := newTime(year, month, day, hour, minute, second, frac)
- if err != nil {
- return ZeroDatetime, errors.Trace(err)
- }
-
- nt := Time{
- Time: t,
- Type: TypeDatetime,
- Fsp: fsp}
-
- return nt, nil
- }
-
- func scanTimeArgs(seps []string, args ...*int) error {
- if len(seps) != len(args) {
- return errors.Trace(ErrInvalidTimeFormat)
- }
-
- var err error
- for i, s := range seps {
- *args[i], err = strconv.Atoi(s)
- if err != nil {
- return errors.Trace(err)
- }
- }
- return nil
- }
-
- // ParseYear parses a formatted string and returns a year number.
- func ParseYear(str string) (int16, error) {
- v, err := strconv.ParseInt(str, 10, 16)
- if err != nil {
- return 0, errors.Trace(err)
- }
- y := int16(v)
-
- if len(str) == 4 {
- // Nothing to do.
- } else if len(str) == 2 || len(str) == 1 {
- y = int16(adjustYear(int(y)))
- } else {
- return 0, errors.Trace(ErrInvalidYearFormat)
- }
-
- if y < MinYear || y > MaxYear {
- return 0, errors.Trace(ErrInvalidYearFormat)
- }
-
- return y, nil
- }
-
- func newTime(year int, month int, day int, hour int, minute int, second int, frac int) (time.Time, error) {
- if year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0 {
- // Should we check fractional fractional here?
- // But go time.Time can not support zero time 0000-00-00 00:00:00.
- return ZeroTime, nil
- }
-
- if err := checkTime(year, month, day, hour, minute, second, frac); err != nil {
- return ZeroTime, errors.Trace(err)
- }
-
- return time.Date(year, time.Month(month), day, hour, minute, second, frac*1000, time.Local), nil
- }
-
- // See https://dev.mysql.com/doc/refman/5.7/en/two-digit-years.html
- func adjustYear(y int) int {
- if y >= 0 && y <= 69 {
- y = 2000 + y
- } else if y >= 70 && y <= 99 {
- y = 1900 + y
- }
- return y
- }
-
- // AdjustYear is used for adjusting year and checking its validation.
- func AdjustYear(y int64) (int64, error) {
- y = int64(adjustYear(int(y)))
- if y < int64(MinYear) || y > int64(MaxYear) {
- return 0, errors.Trace(ErrInvalidYear)
- }
-
- return y, nil
- }
-
- // Duration is the type for MySQL time type.
- type Duration struct {
- time.Duration
- // Fsp is short for Fractional Seconds Precision.
- // See http://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html
- Fsp int
- }
-
- // String returns the time formatted using default TimeFormat and fsp.
- func (d Duration) String() string {
- var buf bytes.Buffer
-
- sign, hours, minutes, seconds, fraction := splitDuration(d.Duration)
- if sign < 0 {
- buf.WriteByte('-')
- }
-
- fmt.Fprintf(&buf, "%02d:%02d:%02d", hours, minutes, seconds)
- if d.Fsp > 0 {
- buf.WriteString(".")
- buf.WriteString(d.formatFrac(fraction))
- }
-
- p := buf.String()
-
- return p
- }
-
- func (d Duration) formatFrac(frac int) string {
- format := fmt.Sprintf("%%0%dd", d.Fsp)
- s := fmt.Sprintf(format, frac)
- return s[0:d.Fsp]
- }
-
- // ToNumber changes duration to number format.
- // e.g,
- // 10:10:10 -> 101010
- func (d Duration) ToNumber() Decimal {
- sign, hours, minutes, seconds, fraction := splitDuration(time.Duration(d.Duration))
- var (
- s string
- signStr string
- )
-
- if sign < 0 {
- signStr = "-"
- }
-
- if d.Fsp == 0 {
- s = fmt.Sprintf("%s%02d%02d%02d", signStr, hours, minutes, seconds)
- } else {
- s = fmt.Sprintf("%s%02d%02d%02d.%s", signStr, hours, minutes, seconds, d.formatFrac(fraction))
- }
-
- // We skip checking error here because time formatted string can be parsed certainly.
- v, _ := ParseDecimal(s)
- return v
- }
-
- // ConvertToTime converts duration to Time.
- // Tp is TypeDatetime, TypeTimestamp and TypeDate.
- func (d Duration) ConvertToTime(tp uint8) (Time, error) {
- year, month, day := time.Now().Date()
- // just use current year, month and day.
- n := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
- n = n.Add(d.Duration)
-
- t := Time{
- Time: n,
- Type: TypeDatetime,
- Fsp: d.Fsp,
- }
-
- return t.Convert(tp)
- }
-
- // RoundFrac rounds fractional seconds precision with new fsp and returns a new one.
- // We will use the “round half up” rule, e.g, >= 0.5 -> 1, < 0.5 -> 0,
- // so 10:10:10.999999 round 0 -> 10:10:11
- // and 10:10:10.000000 round 0 -> 10:10:10
- func (d Duration) RoundFrac(fsp int) (Duration, error) {
- fsp, err := checkFsp(fsp)
- if err != nil {
- return d, errors.Trace(err)
- }
-
- if fsp == d.Fsp {
- return d, nil
- }
-
- n := ZeroTime
- nd := n.Add(d.Duration).Round(time.Duration(math.Pow10(9-fsp)) * time.Nanosecond).Sub(n)
- return Duration{Duration: nd, Fsp: fsp}, nil
- }
-
- // Compare returns an integer comparing the Duration instant t to o.
- // If d is after o, return 1, equal o, return 0, before o, return -1.
- func (d Duration) Compare(o Duration) int {
- if d.Duration > o.Duration {
- return 1
- } else if d.Duration == o.Duration {
- return 0
- } else {
- return -1
- }
- }
-
- // CompareString is like Compare,
- // but parses str to Duration then compares.
- func (d Duration) CompareString(str string) (int, error) {
- // use MaxFsp to parse the string
- o, err := ParseDuration(str, MaxFsp)
- if err != nil {
- return 0, err
- }
-
- return d.Compare(o), nil
- }
-
- // Hour returns current hour.
- // e.g, hour("11:11:11") -> 11
- func (d Duration) Hour() int {
- _, hour, _, _, _ := splitDuration(d.Duration)
- return hour
- }
-
- // Minute returns current minute.
- // e.g, hour("11:11:11") -> 11
- func (d Duration) Minute() int {
- _, _, minute, _, _ := splitDuration(d.Duration)
- return minute
- }
-
- // Second returns current second.
- // e.g, hour("11:11:11") -> 11
- func (d Duration) Second() int {
- _, _, _, second, _ := splitDuration(d.Duration)
- return second
- }
-
- // MicroSecond returns current microsecond.
- // e.g, hour("11:11:11.11") -> 110000
- func (d Duration) MicroSecond() int {
- _, _, _, _, frac := splitDuration(d.Duration)
- return frac
- }
-
- // ParseDuration parses the time form a formatted string with a fractional seconds part,
- // returns the duration type Time value.
- // See: http://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html
- func ParseDuration(str string, fsp int) (Duration, error) {
- var (
- day int
- hour int
- minute int
- second int
- frac int
-
- err error
- sign = 0
- dayExists = false
- )
-
- fsp, err = checkFsp(fsp)
- if err != nil {
- return ZeroDuration, errors.Trace(err)
- }
-
- if len(str) == 0 {
- return ZeroDuration, nil
- } else if str[0] == '-' {
- str = str[1:]
- sign = -1
- }
-
- // Time format may has day.
- if n := strings.IndexByte(str, ' '); n >= 0 {
- if day, err = strconv.Atoi(str[:n]); err == nil {
- dayExists = true
- }
- str = str[n+1:]
- }
-
- if n := strings.IndexByte(str, '.'); n >= 0 {
- // It has fractional precesion parts.
- fracStr := str[n+1:]
- frac, err = parseFrac(fracStr, fsp)
- if err != nil {
- return ZeroDuration, errors.Trace(err)
- }
- str = str[0:n]
- }
-
- // It tries to split str with delimiter, time delimiter must be :
- seps := strings.Split(str, ":")
-
- switch len(seps) {
- case 1:
- if dayExists {
- hour, err = strconv.Atoi(seps[0])
- } else {
- // No delimiter.
- if len(str) == 6 {
- // HHMMSS
- _, err = fmt.Sscanf(str, "%2d%2d%2d", &hour, &minute, &second)
- } else if len(str) == 4 {
- // MMSS
- _, err = fmt.Sscanf(str, "%2d%2d", &minute, &second)
- } else if len(str) == 2 {
- // SS
- _, err = fmt.Sscanf(str, "%2d", &second)
- } else {
- // Maybe only contains date.
- _, err = ParseDate(str)
- if err == nil {
- return ZeroDuration, nil
- }
- return ZeroDuration, errors.Trace(ErrInvalidTimeFormat)
- }
- }
- case 2:
- // HH:MM
- _, err = fmt.Sscanf(str, "%2d:%2d", &hour, &minute)
- case 3:
- // Time format maybe HH:MM:SS or HHH:MM:SS.
- // See: https://dev.mysql.com/doc/refman/5.7/en/time.html
- if !dayExists && len(seps[0]) == 3 {
- _, err = fmt.Sscanf(str, "%3d:%2d:%2d", &hour, &minute, &second)
- } else {
- _, err = fmt.Sscanf(str, "%2d:%2d:%2d", &hour, &minute, &second)
- }
- default:
- return ZeroDuration, errors.Trace(ErrInvalidTimeFormat)
- }
-
- if err != nil {
- return ZeroDuration, errors.Trace(err)
- }
-
- d := time.Duration(day*24*3600+hour*3600+minute*60+second)*time.Second + time.Duration(frac)*time.Microsecond
- if sign == -1 {
- d = -d
- }
-
- if d > MaxTime {
- d = MaxTime
- err = ErrInvalidTimeFormat
- } else if d < MinTime {
- d = MinTime
- err = ErrInvalidTimeFormat
- }
- return Duration{Duration: d, Fsp: fsp}, errors.Trace(err)
- }
-
- func splitDuration(t time.Duration) (int, int, int, int, int) {
- sign := 1
- if t < 0 {
- t = -t
- sign = -1
- }
-
- hours := t / time.Hour
- t -= hours * time.Hour
- minutes := t / time.Minute
- t -= minutes * time.Minute
- seconds := t / time.Second
- t -= seconds * time.Second
- fraction := t / time.Microsecond
-
- return sign, int(hours), int(minutes), int(seconds), int(fraction)
- }
-
- func checkTime(year int, month int, day int, hour int, minute int, second int, frac int) error {
- // Notes: for datetime type, `insert t values("0001-01-01 00:00:00");` is valid
- // so here only check year from 0~9999.
- if (year < 0 || year > 9999) ||
- (month <= 0 || month > 12) ||
- (day <= 0 || day > 31) ||
- (hour < 0 || hour >= 24) ||
- (minute < 0 || minute >= 60) ||
- (second < 0 || second >= 60) ||
- (frac < 0) {
- return errors.Trace(ErrInvalidTimeFormat)
- }
-
- return nil
- }
-
- func getTime(num int64, tp byte) (Time, error) {
- s1 := num / 1000000
- s2 := num - s1*1000000
-
- year := int(s1 / 10000)
- s1 %= 10000
- month := int(s1 / 100)
- day := int(s1 % 100)
-
- hour := int(s2 / 10000)
- s2 %= 10000
- minute := int(s2 / 100)
- second := int(s2 % 100)
-
- if err := checkTime(year, month, day, hour, minute, second, 0); err != nil {
- return Time{
- Time: ZeroTime,
- Type: tp,
- Fsp: DefaultFsp,
- }, err
- }
-
- t, err := newTime(year, month, day, hour, minute, second, 0)
- return Time{
- Time: t,
- Type: tp,
- Fsp: DefaultFsp,
- }, errors.Trace(err)
- }
-
- // See number_to_datetime function.
- // https://github.com/mysql/mysql-server/blob/5.7/sql-common/my_time.c
- func parseDateTimeFromNum(num int64) (Time, error) {
- t := ZeroDate
- // Check zero.
- if num == 0 {
- return t, nil
- }
-
- // Check datetime type.
- if num >= 10000101000000 {
- return getTime(num, t.Type)
- }
-
- // Check MMDD.
- if num < 101 {
- return t, errors.Trace(ErrInvalidTimeFormat)
- }
-
- // Adjust year
- // YYMMDD, year: 2000-2069
- if num <= (70-1)*10000+1231 {
- num = (num + 20000000) * 1000000
- return getTime(num, t.Type)
- }
-
- // Check YYMMDD.
- if num < 70*10000+101 {
- return t, errors.Trace(ErrInvalidTimeFormat)
- }
-
- // Adjust year
- // YYMMDD, year: 1970-1999
- if num < 991231 {
- num = (num + 19000000) * 1000000
- return getTime(num, t.Type)
- }
-
- // Check YYYYMMDD.
- if num < 10000101 {
- return t, errors.Trace(ErrInvalidTimeFormat)
- }
-
- // Adjust hour/min/second.
- if num < 99991231 {
- num = num * 1000000
- return getTime(num, t.Type)
- }
-
- // Check MMDDHHMMSS.
- if num < 101000000 {
- return t, errors.Trace(ErrInvalidTimeFormat)
- }
-
- // Set TypeDatetime type.
- t.Type = TypeDatetime
-
- // Adjust year
- // YYMMDDHHMMSS, 2000-2069
- if num <= 69*10000000000+1231235959 {
- num = num + 20000000000000
- return getTime(num, t.Type)
- }
-
- // Check YYYYMMDDHHMMSS.
- if num < 70*10000000000+101000000 {
- return t, errors.Trace(ErrInvalidTimeFormat)
- }
-
- // Adjust year
- // YYMMDDHHMMSS, 1970-1999
- if num <= 991231235959 {
- num = num + 19000000000000
- return getTime(num, t.Type)
- }
-
- return getTime(num, t.Type)
- }
-
- // ParseTime parses a formatted string with type tp and specific fsp.
- // Type is TypeDatetime, TypeTimestamp and TypeDate.
- // Fsp is in range [0, 6].
- // MySQL supports many valid datatime format, but still has some limitation.
- // If delimiter exists, the date part and time part is seperated by a space or T,
- // other punctuation character can be used as the delimiter between date parts or time parts.
- // If no delimiter, the format must be YYYYMMDDHHMMSS or YYMMDDHHMMSS
- // If we have fractional seconds part, we must use decimal points as the delimiter.
- // The valid datetime range is from '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999'.
- // The valid timestamp range is from '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'.
- // The valid date range is from '1000-01-01' to '9999-12-31'
- func ParseTime(str string, tp byte, fsp int) (Time, error) {
- fsp, err := checkFsp(fsp)
- if err != nil {
- return Time{Time: ZeroTime, Type: tp}, errors.Trace(err)
- }
-
- t, err := parseDatetime(str, fsp)
- if err != nil {
- return Time{Time: ZeroTime, Type: tp}, errors.Trace(err)
- }
-
- return t.Convert(tp)
- }
-
- // ParseDatetime is a helper function wrapping ParseTime with datetime type and default fsp.
- func ParseDatetime(str string) (Time, error) {
- return ParseTime(str, TypeDatetime, DefaultFsp)
- }
-
- // ParseTimestamp is a helper function wrapping ParseTime with timestamp type and default fsp.
- func ParseTimestamp(str string) (Time, error) {
- return ParseTime(str, TypeTimestamp, DefaultFsp)
- }
-
- // ParseDate is a helper function wrapping ParseTime with date type.
- func ParseDate(str string) (Time, error) {
- // date has no fractional seconds precision
- return ParseTime(str, TypeDate, MinFsp)
- }
-
- // ParseTimeFromNum parses a formatted int64,
- // returns the value which type is tp.
- func ParseTimeFromNum(num int64, tp byte, fsp int) (Time, error) {
- fsp, err := checkFsp(fsp)
- if err != nil {
- return Time{Time: ZeroTime, Type: tp}, errors.Trace(err)
- }
-
- t, err := parseDateTimeFromNum(num)
- if err != nil {
- return Time{Time: ZeroTime, Type: tp}, errors.Trace(err)
- }
-
- if !checkDatetime(t) {
- return Time{Time: ZeroTime, Type: tp}, ErrInvalidTimeFormat
- }
-
- t.Fsp = fsp
- return t.Convert(tp)
- }
-
- // ParseDatetimeFromNum is a helper function wrapping ParseTimeFromNum with datetime type and default fsp.
- func ParseDatetimeFromNum(num int64) (Time, error) {
- return ParseTimeFromNum(num, TypeDatetime, DefaultFsp)
- }
-
- // ParseTimestampFromNum is a helper function wrapping ParseTimeFromNum with timestamp type and default fsp.
- func ParseTimestampFromNum(num int64) (Time, error) {
- return ParseTimeFromNum(num, TypeTimestamp, DefaultFsp)
- }
-
- // ParseDateFromNum is a helper function wrapping ParseTimeFromNum with date type.
- func ParseDateFromNum(num int64) (Time, error) {
- // date has no fractional seconds precision
- return ParseTimeFromNum(num, TypeDate, MinFsp)
- }
-
- func checkDatetime(t Time) bool {
- if t.IsZero() {
- return true
- }
-
- if t.Time.After(MaxDatetime) || t.Time.Before(MinDatetime) {
- return false
- }
-
- return true
- }
-
- func checkTimestamp(t Time) bool {
- if t.IsZero() {
- return true
- }
-
- if t.Time.After(MaxTimestamp) || t.Time.Before(MinTimestamp) {
- return false
- }
-
- return true
- }
-
- // ExtractTimeNum extracts time value number from time unit and format.
- func ExtractTimeNum(unit string, t Time) (int64, error) {
- switch strings.ToUpper(unit) {
- case "MICROSECOND":
- return int64(t.Nanosecond() / 1000), nil
- case "SECOND":
- return int64(t.Second()), nil
- case "MINUTE":
- return int64(t.Minute()), nil
- case "HOUR":
- return int64(t.Hour()), nil
- case "DAY":
- return int64(t.Day()), nil
- case "WEEK":
- _, week := t.ISOWeek()
- return int64(week), nil
- case "MONTH":
- return int64(t.Month()), nil
- case "QUARTER":
- m := int64(t.Month())
- // 1 - 3 -> 1
- // 4 - 6 -> 2
- // 7 - 9 -> 3
- // 10 - 12 -> 4
- return (m + 2) / 3, nil
- case "YEAR":
- return int64(t.Year()), nil
- case "SECOND_MICROSECOND":
- return int64(t.Second())*1000000 + int64(t.Nanosecond())/1000, nil
- case "MINUTE_MICROSECOND":
- _, m, s := t.Clock()
- return int64(m)*100000000 + int64(s)*1000000 + int64(t.Nanosecond())/1000, nil
- case "MINUTE_SECOND":
- _, m, s := t.Clock()
- return int64(m*100 + s), nil
- case "HOUR_MICROSECOND":
- h, m, s := t.Clock()
- return int64(h)*10000000000 + int64(m)*100000000 + int64(s)*1000000 + int64(t.Nanosecond())/1000, nil
- case "HOUR_SECOND":
- h, m, s := t.Clock()
- return int64(h)*10000 + int64(m)*100 + int64(s), nil
- case "HOUR_MINUTE":
- h, m, _ := t.Clock()
- return int64(h)*100 + int64(m), nil
- case "DAY_MICROSECOND":
- h, m, s := t.Clock()
- d := t.Day()
- return int64(d*1000000+h*10000+m*100+s)*1000000 + int64(t.Nanosecond())/1000, nil
- case "DAY_SECOND":
- h, m, s := t.Clock()
- d := t.Day()
- return int64(d)*1000000 + int64(h)*10000 + int64(m)*100 + int64(s), nil
- case "DAY_MINUTE":
- h, m, _ := t.Clock()
- d := t.Day()
- return int64(d)*10000 + int64(h)*100 + int64(m), nil
- case "DAY_HOUR":
- h, _, _ := t.Clock()
- d := t.Day()
- return int64(d)*100 + int64(h), nil
- case "YEAR_MONTH":
- y, m, _ := t.Date()
- return int64(y)*100 + int64(m), nil
- default:
- return 0, errors.Errorf("invalid unit %s", unit)
- }
- }
-
- func extractSingleTimeValue(unit string, format string) (int64, int64, int64, time.Duration, error) {
- iv, err := strconv.ParseInt(format, 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- v := time.Duration(iv)
- switch strings.ToUpper(unit) {
- case "MICROSECOND":
- return 0, 0, 0, v * time.Microsecond, nil
- case "SECOND":
- return 0, 0, 0, v * time.Second, nil
- case "MINUTE":
- return 0, 0, 0, v * time.Minute, nil
- case "HOUR":
- return 0, 0, 0, v * time.Hour, nil
- case "DAY":
- return 0, 0, iv, 0, nil
- case "WEEK":
- return 0, 0, 7 * iv, 0, nil
- case "MONTH":
- return 0, iv, 0, 0, nil
- case "QUARTER":
- return 0, 3 * iv, 0, 0, nil
- case "YEAR":
- return iv, 0, 0, 0, nil
- }
-
- return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit)
- }
-
- // Format is `SS.FFFFFF`.
- func extractSecondMicrosecond(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, ".")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- seconds, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- microseconds, err := strconv.ParseInt(alignFrac(fields[1], MaxFsp), 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return 0, 0, 0, time.Duration(seconds)*time.Second + time.Duration(microseconds)*time.Microsecond, nil
- }
-
- // Format is `MM:SS.FFFFFF`.
- func extractMinuteMicrosecond(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, ":")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- minutes, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- _, _, _, value, err := extractSecondMicrosecond(fields[1])
- if err != nil {
- return 0, 0, 0, 0, errors.Trace(err)
- }
-
- return 0, 0, 0, time.Duration(minutes)*time.Minute + value, nil
- }
-
- // Format is `MM:SS`.
- func extractMinuteSecond(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, ":")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- minutes, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- seconds, err := strconv.ParseInt(fields[1], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return 0, 0, 0, time.Duration(minutes)*time.Minute + time.Duration(seconds)*time.Second, nil
- }
-
- // Format is `HH:MM:SS.FFFFFF`.
- func extractHourMicrosecond(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, ":")
- if len(fields) != 3 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- hours, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- minutes, err := strconv.ParseInt(fields[1], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- _, _, _, value, err := extractSecondMicrosecond(fields[2])
- if err != nil {
- return 0, 0, 0, 0, errors.Trace(err)
- }
-
- return 0, 0, 0, time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute + value, nil
- }
-
- // Format is `HH:MM:SS`.
- func extractHourSecond(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, ":")
- if len(fields) != 3 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- hours, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- minutes, err := strconv.ParseInt(fields[1], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- seconds, err := strconv.ParseInt(fields[2], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return 0, 0, 0, time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute + time.Duration(seconds)*time.Second, nil
- }
-
- // Format is `HH:MM`.
- func extractHourMinute(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, ":")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- hours, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- minutes, err := strconv.ParseInt(fields[1], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return 0, 0, 0, time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute, nil
- }
-
- // Format is `DD HH:MM:SS.FFFFFF`.
- func extractDayMicrosecond(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, " ")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- days, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- _, _, _, value, err := extractHourMicrosecond(fields[1])
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return 0, 0, days, value, nil
- }
-
- // Format is `DD HH:MM:SS`.
- func extractDaySecond(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, " ")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- days, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- _, _, _, value, err := extractHourSecond(fields[1])
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return 0, 0, days, value, nil
- }
-
- // Format is `DD HH:MM`.
- func extractDayMinute(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, " ")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- days, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- _, _, _, value, err := extractHourMinute(fields[1])
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return 0, 0, days, value, nil
- }
-
- // Format is `DD HH`.
- func extractDayHour(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, " ")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- days, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- hours, err := strconv.ParseInt(fields[1], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return 0, 0, days, time.Duration(hours) * time.Hour, nil
- }
-
- // Format is `YYYY-MM`.
- func extractYearMonth(format string) (int64, int64, int64, time.Duration, error) {
- fields := strings.Split(format, "-")
- if len(fields) != 2 {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- years, err := strconv.ParseInt(fields[0], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- months, err := strconv.ParseInt(fields[1], 10, 64)
- if err != nil {
- return 0, 0, 0, 0, errors.Errorf("invalid time format - %s", format)
- }
-
- return years, months, 0, 0, nil
- }
-
- // ExtractTimeValue extracts time value from time unit and format.
- func ExtractTimeValue(unit string, format string) (int64, int64, int64, time.Duration, error) {
- switch strings.ToUpper(unit) {
- case "MICROSECOND", "SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "QUARTER", "YEAR":
- return extractSingleTimeValue(unit, format)
- case "SECOND_MICROSECOND":
- return extractSecondMicrosecond(format)
- case "MINUTE_MICROSECOND":
- return extractMinuteMicrosecond(format)
- case "MINUTE_SECOND":
- return extractMinuteSecond(format)
- case "HOUR_MICROSECOND":
- return extractHourMicrosecond(format)
- case "HOUR_SECOND":
- return extractHourSecond(format)
- case "HOUR_MINUTE":
- return extractHourMinute(format)
- case "DAY_MICROSECOND":
- return extractDayMicrosecond(format)
- case "DAY_SECOND":
- return extractDaySecond(format)
- case "DAY_MINUTE":
- return extractDayMinute(format)
- case "DAY_HOUR":
- return extractDayHour(format)
- case "YEAR_MONTH":
- return extractYearMonth(format)
- default:
- return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit)
- }
- }
-
- // IsClockUnit returns true when unit is interval unit with hour, minute or second.
- func IsClockUnit(unit string) bool {
- switch strings.ToUpper(unit) {
- case "MICROSECOND", "SECOND", "MINUTE", "HOUR",
- "SECOND_MICROSECOND", "MINUTE_MICROSECOND", "MINUTE_SECOND",
- "HOUR_MICROSECOND", "HOUR_SECOND", "HOUR_MINUTE",
- "DAY_MICROSECOND", "DAY_SECOND", "DAY_MINUTE", "DAY_HOUR":
- return true
- default:
- return false
- }
- }
-
- // IsDateFormat returns true when the specified time format could contain only date.
- func IsDateFormat(format string) bool {
- format = strings.TrimSpace(format)
- seps := parseDateFormat(format)
- length := len(format)
- switch len(seps) {
- case 1:
- if (length == 8) || (length == 6) {
- return true
- }
- case 3:
- return true
- }
- return false
- }
-
- // ParseTimeFromInt64 parses mysql time value from int64.
- func ParseTimeFromInt64(num int64) (Time, error) {
- return parseDateTimeFromNum(num)
- }
|