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