github.com/dolthub/go-mysql-server@v0.18.0/sql/planbuilder/dateparse/date.go (about)

     1  package dateparse
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  )
     8  
     9  var (
    10  	dateSpecifiers = []uint8{'a', 'b', 'c', 'D', 'd', 'e', 'j', 'M', 'm', 'U', 'u', 'V', 'v', 'W', 'w', 'X', 'x', 'Y', 'y'}
    11  	timeSpecifiers = []uint8{'f', 'H', 'h', 'I', 'i', 'k', 'l', 'p', 'r', 'S', 's', 'T'}
    12  )
    13  
    14  // ParseDateWithFormat parses the date string according to the given
    15  // format string, as defined in the MySQL specification.
    16  //
    17  // Reference the MySQL docs for valid format specifiers.
    18  // This implementation attempts to match the spec to the extent possible.
    19  //
    20  // More info: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format
    21  //
    22  // Even more info: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_str-to-date
    23  func ParseDateWithFormat(date, format string) (interface{}, error) {
    24  	parsers, specifiers, err := parsersFromFormatString(format)
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	hasDate := false
    30  	for _, s := range dateSpecifiers {
    31  		if _, ok := specifiers[s]; ok {
    32  			hasDate = true
    33  			break
    34  		}
    35  	}
    36  
    37  	hasTime := false
    38  	_, hasAmPm := specifiers['p']
    39  	for _, s := range timeSpecifiers {
    40  		if _, ok := specifiers[s]; ok {
    41  			// validate that am/pm is not used with 24 hour time specifiers
    42  			if (s == 'H' || s == 'k' || s == 'T') && hasAmPm {
    43  				return nil, fmt.Errorf("cannot use 24 hour time (H) with AM/PM (p)")
    44  			}
    45  			hasTime = true
    46  			break
    47  		}
    48  	}
    49  
    50  	// trim all leading and trailing whitespace
    51  	date = strings.TrimSpace(date)
    52  
    53  	// convert to all lowercase
    54  	date = strings.ToLower(date)
    55  
    56  	var dt datetime
    57  	target := date
    58  	for _, parser := range parsers {
    59  		target = takeAllSpaces(target)
    60  		rest, err := parser(&dt, target)
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  		target = rest
    65  	}
    66  
    67  	var result string
    68  	if hasDate && hasTime {
    69  		result = fmt.Sprintf("%s %s", evaluateDate(dt), evaluateTime(dt))
    70  	} else if hasTime {
    71  		result = fmt.Sprintf("%s", evaluateTime(dt))
    72  	} else if hasDate {
    73  		result = fmt.Sprintf("%s", evaluateDate(dt))
    74  	} else {
    75  		return nil, fmt.Errorf("no value to evaluate")
    76  	}
    77  
    78  	return result, nil
    79  }
    80  
    81  // Convert the user-defined format string into a slice of parser functions
    82  // which will later process the date string.
    83  //
    84  // Example format string: "%H:%i:%s".
    85  func parsersFromFormatString(format string) ([]parser, map[uint8]bool, error) {
    86  	parsers := make([]parser, 0, len(format))
    87  	var specifiersInFormat = make(map[uint8]bool)
    88  	for i := 0; i < len(format); i++ {
    89  		char := format[i]
    90  		if char == '%' {
    91  			if len(format) <= i+1 {
    92  				return nil, nil, fmt.Errorf("\"%%\" found at end of format string")
    93  			}
    94  			specifier := format[i+1]
    95  			specifiersInFormat[specifier] = true
    96  			parser, ok := formatSpecifiers[specifier]
    97  			if !ok {
    98  				return nil, nil, fmt.Errorf("unknown format specifier \"%c\"", specifier)
    99  			}
   100  			if parser == nil {
   101  				return nil, nil, fmt.Errorf("format specifier \"%c\" not yet supported", specifier)
   102  			}
   103  			parsers = append(parsers, wrapSpecifierParser(parser, specifier))
   104  
   105  			// both the '%' and the specifier are consumed
   106  			i++
   107  		} else {
   108  			parsers = append(parsers, wrapLiteralParser(char))
   109  		}
   110  	}
   111  
   112  	return parsers, specifiersInFormat, nil
   113  }
   114  
   115  // Wrap a literal char parser, returning the corresponding
   116  // typed error on failures.
   117  func wrapLiteralParser(literal byte) parser {
   118  	return func(result *datetime, chars string) (rest string, err error) {
   119  		rest, err = literalParser(literal)(result, chars)
   120  		if err != nil {
   121  			return "", ParseLiteralErr{
   122  				Literal: literal,
   123  				Tokens:  chars,
   124  				err:     err,
   125  			}
   126  		}
   127  		return rest, nil
   128  	}
   129  }
   130  
   131  // Wrap a format specifier parser, returning the corresponding
   132  // typed error on failures.
   133  func wrapSpecifierParser(p parser, specifier byte) parser {
   134  	return func(result *datetime, chars string) (rest string, err error) {
   135  		rest, err = p(result, chars)
   136  		if err != nil {
   137  			return "", ParseSpecifierErr{
   138  				Specifier: specifier,
   139  				Tokens:    chars,
   140  				err:       err,
   141  			}
   142  		}
   143  		return rest, nil
   144  	}
   145  }
   146  
   147  // datetime defines the fields parsed by format specifiers.
   148  // Some combinations of values are invalid and cannot be mapped
   149  // unambiguously to time.Time.
   150  //
   151  // Unspecified values are nil.
   152  type datetime struct {
   153  	day   *uint
   154  	month *time.Month
   155  	year  *uint
   156  
   157  	dayOfYear  *uint
   158  	weekOfYear *uint
   159  
   160  	// this is completely ignored, but we still parse it for correctness
   161  	weekday *time.Weekday
   162  
   163  	// true => AM, false => PM, nil => unspecified
   164  	am *bool
   165  
   166  	hours        *uint
   167  	minutes      *uint
   168  	seconds      *uint
   169  	milliseconds *uint
   170  	microseconds *uint
   171  	nanoseconds  *uint
   172  }
   173  
   174  func (dt *datetime) isEmpty() bool {
   175  	if dt.day == nil && dt.month == nil && dt.year == nil && dt.dayOfYear == nil && dt.weekOfYear == nil && dt.weekday == nil && dt.am == nil && dt.hours == nil && dt.minutes == nil && dt.seconds == nil && dt.milliseconds == nil && dt.microseconds == nil && dt.nanoseconds == nil {
   176  		return true
   177  	}
   178  	return false
   179  }
   180  
   181  // ParseSpecifierErr defines a error when attempting to parse
   182  // the date string input according to a specified format directive.
   183  type ParseSpecifierErr struct {
   184  	Specifier byte
   185  	Tokens    string
   186  	err       error
   187  }
   188  
   189  func (p ParseSpecifierErr) Unwrap() error { return p.err }
   190  
   191  func (p ParseSpecifierErr) Error() string {
   192  	return fmt.Sprintf("specifier %%%c failed to parse \"%s\": %s", p.Specifier, p.Tokens, p.err.Error())
   193  }
   194  
   195  // ParseLiteralErr defines a error when attempting to parse
   196  // the date string input according to a literal character specified
   197  // in the format string.
   198  type ParseLiteralErr struct {
   199  	Literal byte
   200  	Tokens  string
   201  	err     error
   202  }
   203  
   204  func (p ParseLiteralErr) Unwrap() error { return p.err }
   205  
   206  func (p ParseLiteralErr) Error() string {
   207  	return fmt.Sprintf("literal %c not matched in \"%s\": %s", p.Literal, p.Tokens, p.err.Error())
   208  }
   209  
   210  // formatSpecifiers defines the formatting directives for parsing and formatting dates.
   211  //
   212  // Reference: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format
   213  var formatSpecifiers = map[byte]parser{
   214  	// %a	Abbreviated weekday name (Sun..Sat)
   215  	'a': parseWeekdayAbbreviation,
   216  	// %b	Abbreviated month name (Jan..Dec)
   217  	'b': parseMonthAbbreviation,
   218  	// %c	Month, numeric (0..12)
   219  	'c': parseMonthNumeric,
   220  	// %D	Day of the month with English suffix (0th, 1st, 2nd, 3rd, …)
   221  	'D': parseDayNumericWithEnglishSuffix,
   222  	// %d	Day of the month, numeric (00..31)
   223  	'd': parseDayOfMonthNumeric,
   224  	// %e	Day of the month, numeric (0..31)
   225  	'e': parseDayOfMonthNumeric,
   226  	// %f	Microseconds (000000..999999)
   227  	'f': parseMicrosecondsNumeric,
   228  	// %H	Hour (00..23)
   229  	'H': parse24HourNumeric,
   230  	// %h	Hour (01..12)
   231  	'h': parse12HourNumeric,
   232  	// %I	Hour (01..12)
   233  	'I': parse12HourNumeric,
   234  	// %i	Minutes, numeric (00..59)
   235  	'i': parseMinuteNumeric,
   236  	// %j	Day of year (001..366)
   237  	'j': parseDayOfYearNumeric,
   238  	// %k	Hour (0..23)
   239  	'k': parse24HourNumeric,
   240  	// %l	Hour (1..12)
   241  	'l': parse12HourNumeric,
   242  	// %M	Month name (January..December)
   243  	'M': parseMonthName,
   244  	// %m	Month, numeric (00..12)
   245  	'm': parseMonthNumeric,
   246  	// %p	AM or PM
   247  	'p': parseAmPm,
   248  	// %r	Time, 12-hour (hh:mm:ss followed by AM or PM)
   249  	'r': parse12HourTimestamp,
   250  	// %S	Seconds (00..59)
   251  	'S': parseSecondsNumeric,
   252  	// %s	Seconds (00..59)
   253  	's': parseSecondsNumeric,
   254  	// %T	Time, 24-hour (hh:mm:ss)
   255  	'T': parse24HourTimestamp,
   256  	'U': nil,
   257  	'u': nil,
   258  	'V': nil,
   259  	'v': nil,
   260  	'W': nil,
   261  	'w': nil,
   262  	'X': nil,
   263  	'x': nil,
   264  	// %Y	Year, numeric, four digits
   265  	'Y': parseYear4DigitNumeric,
   266  	// %y	Year, numeric (two digits)
   267  	'y': parseYear2DigitNumeric,
   268  	'%': literalParser('%'),
   269  }
   270  
   271  func boolPtr(a bool) *bool { return &a }
   272  
   273  // Convert a week abbreviation to a defined weekday.
   274  func weekdayAbbrev(abbrev string) (time.Weekday, bool) {
   275  	switch abbrev {
   276  	case "sun":
   277  		return time.Sunday, true
   278  	case "mon":
   279  		return time.Monday, true
   280  	case "tue":
   281  		return time.Tuesday, true
   282  	case "wed":
   283  		return time.Wednesday, true
   284  	case "thu":
   285  		return time.Thursday, true
   286  	case "fri":
   287  		return time.Friday, true
   288  	case "sat":
   289  		return time.Saturday, true
   290  	}
   291  	return 0, false
   292  }
   293  
   294  // Convert a month abbreviation to a defined month.
   295  func monthAbbrev(abbrev string) (time.Month, bool) {
   296  	switch abbrev {
   297  	case "jan":
   298  		return time.January, true
   299  	case "feb":
   300  		return time.February, true
   301  	case "mar":
   302  		return time.March, true
   303  	case "apr":
   304  		return time.April, true
   305  	case "may":
   306  		return time.May, true
   307  	case "jun":
   308  		return time.June, true
   309  	case "jul":
   310  		return time.July, true
   311  	case "aug":
   312  		return time.August, true
   313  	case "sep":
   314  		return time.September, true
   315  	case "oct":
   316  		return time.October, true
   317  	case "nov":
   318  		return time.November, true
   319  	case "dec":
   320  		return time.December, true
   321  	}
   322  	return 0, false
   323  }
   324  
   325  // TODO: allow this to match partial months
   326  // janu should match janurary
   327  func monthName(name string) (month time.Month, charCount int, ok bool) {
   328  	for i := 1; i < 13; i++ {
   329  		m := time.Month(i)
   330  		if strings.HasPrefix(name, strings.ToLower(m.String())) {
   331  			return m, len(m.String()), true
   332  		}
   333  	}
   334  	return 0, 0, false
   335  }
   336  
   337  // MySQL specification, valid format specifiers.
   338  // Specifier	Description
   339  // %a			Abbreviated weekday name (Sun..Sat)
   340  // %b			Abbreviated month name (Jan..Dec)
   341  // %c			Month, numeric (0..12)
   342  // %D			Day of the month with English suffix (0th, 1st, 2nd, 3rd, …)
   343  // %d			Day of the month, numeric (00..31)
   344  // %e			Day of the month, numeric (0..31)
   345  // %f			Microseconds (000000..999999)
   346  // %H			Hour (00..23)
   347  // %h			Hour (01..12)
   348  // %I			Hour (01..12)
   349  // %i			Minutes, numeric (00..59)
   350  // %j			Day of year (001..366)
   351  // %k			Hour (0..23)
   352  // %l			Hour (1..12)
   353  // %M			Month name (January..December)
   354  // %m			Month, numeric (00..12)
   355  // %p			AM or PM
   356  // %r			Time, 12-hour (hh:mm:ss followed by AM or PM)
   357  // %S			Seconds (00..59)
   358  // %s			Seconds (00..59)
   359  // %T			Time, 24-hour (hh:mm:ss)
   360  // %U			Week (00..53), where Sunday is the first day of the week; WEEK() mode 0
   361  // %u			Week (00..53), where Monday is the first day of the week; WEEK() mode 1
   362  // %V			Week (01..53), where Sunday is the first day of the week; WEEK() mode 2; used with %X
   363  // %v			Week (01..53), where Monday is the first day of the week; WEEK() mode 3; used with %x
   364  // %W			Weekday name (Sunday..Saturday)
   365  // %w			Day of the week (0=Sunday..6=Saturday)
   366  // %X			Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V
   367  // %x			Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v
   368  // %Y			Year, numeric, four digits
   369  // %y			Year, numeric (two digits)
   370  // %%			A literal % character