github.com/chain5j/chain5j-pkg@v1.0.7/util/dateutil/date2.go (about) 1 // Package dateutil 2 // 3 // @author: xwc1125 4 package dateutil 5 6 import ( 7 "fmt" 8 "strconv" 9 "strings" 10 "time" 11 "unicode" 12 ) 13 14 type dateState int 15 16 const ( 17 stateStart dateState = iota 18 stateDigit 19 stateDigitDash 20 stateDigitDashAlpha 21 stateDigitDashWs 22 stateDigitDashWsWs 23 stateDigitDashWsWsAMPMMaybe 24 stateDigitDashWsWsOffset 25 stateDigitDashWsWsOffsetAlpha 26 stateDigitDashWsWsOffsetColonAlpha 27 stateDigitDashWsWsOffsetColon 28 stateDigitDashWsOffset 29 stateDigitDashWsWsAlpha 30 stateDigitDashWsPeriod 31 stateDigitDashWsPeriodAlpha 32 stateDigitDashWsPeriodOffset 33 stateDigitDashWsPeriodOffsetAlpha 34 stateDigitDashT 35 stateDigitDashTZ 36 stateDigitDashTZDigit 37 stateDigitDashTOffset 38 stateDigitDashTOffsetColon 39 stateDigitSlash 40 stateDigitSlashWS 41 stateDigitSlashWSColon 42 stateDigitSlashWSColonAMPM 43 stateDigitSlashWSColonColon 44 stateDigitSlashWSColonColonAMPM 45 stateDigitAlpha 46 stateAlpha 47 stateAlphaWS 48 stateAlphaWSDigitComma 49 stateAlphaWSAlpha 50 stateAlphaWSAlphaColon 51 stateAlphaWSAlphaColonOffset 52 stateAlphaWSAlphaColonAlpha 53 stateAlphaWSAlphaColonAlphaOffset 54 stateAlphaWSAlphaColonAlphaOffsetAlpha 55 stateWeekdayComma 56 stateWeekdayCommaOffset 57 stateWeekdayAbbrevComma 58 stateWeekdayAbbrevCommaOffset 59 stateWeekdayAbbrevCommaOffsetZone 60 stateHowLongAgo 61 ) 62 63 const ( 64 Day = time.Hour * 24 65 ) 66 67 var ( 68 shortDates = []string{"01/02/2006", "1/2/2006", "06/01/02", "01/02/06", "1/2/06"} 69 ) 70 71 // ParseAny parse an unknown date format, detect the layout, parse. 72 // Normal parse. Equivalent Timezone rules as time.Parse() 73 func ParseAny(datestr string) (time.Time, error) { 74 return parseTime(datestr, nil) 75 } 76 77 // ParseIn with Location, equivalent to time.ParseInLocation() timezone/offset 78 // rules. Using location arg, if timezone/offset info exists in the 79 // datestring, it uses the given location rules for any zone interpretation. 80 // That is, MST means one thing when using America/Denver and something else 81 // in other locations. 82 func ParseIn(datestr string, loc *time.Location) (time.Time, error) { 83 return parseTime(datestr, loc) 84 } 85 86 // ParseLocal Given an unknown date format, detect the layout, 87 // using time.Local, parse. 88 // 89 // Set Location to time.Local. Same as ParseIn Location but lazily uses 90 // the global time.Local variable for Location argument. 91 // 92 // denverLoc, _ := time.LoadLocation("America/Denver") 93 // time.Local = denverLoc 94 // 95 // t, err := dateparse.ParseLocal("3/1/2014") 96 // 97 // Equivalent to: 98 // 99 // t, err := dateparse.ParseIn("3/1/2014", denverLoc) 100 // 101 func ParseLocal(datestr string) (time.Time, error) { 102 return parseTime(datestr, time.Local) 103 } 104 105 // MustParse parse a date, and panic if it can't be parsed. Used for testing. 106 // Not recommended for most use-cases. 107 func MustParse(datestr string) time.Time { 108 t, err := parseTime(datestr, nil) 109 if err != nil { 110 panic(err.Error()) 111 } 112 return t 113 } 114 115 func parse(layout, datestr string, loc *time.Location) (time.Time, error) { 116 if loc == nil { 117 return time.Parse(layout, datestr) 118 } 119 return time.ParseInLocation(layout, datestr, loc) 120 } 121 122 func parseTime(datestr string, loc *time.Location) (time.Time, error) { 123 state := stateStart 124 125 firstSlash := 0 126 127 // General strategy is to read rune by rune through the date looking for 128 // certain hints of what type of date we are dealing with. 129 // Hopefully we only need to read about 5 or 6 bytes before 130 // we figure it out and then attempt a parse 131 iterRunes: 132 for i := 0; i < len(datestr); i++ { 133 r := rune(datestr[i]) 134 // r, bytesConsumed := utf8.DecodeRuneInString(datestr[ri:]) 135 // if bytesConsumed > 1 { 136 // ri += (bytesConsumed - 1) 137 // } 138 139 switch state { 140 case stateStart: 141 if unicode.IsDigit(r) { 142 state = stateDigit 143 } else if unicode.IsLetter(r) { 144 state = stateAlpha 145 } 146 case stateDigit: // starts digits 147 if unicode.IsDigit(r) { 148 continue 149 } else if unicode.IsLetter(r) { 150 state = stateDigitAlpha 151 continue 152 } 153 switch r { 154 case '-', '\u2212': 155 state = stateDigitDash 156 case '/': 157 state = stateDigitSlash 158 firstSlash = i 159 } 160 case stateDigitDash: // starts digit then dash 02- 161 // 2006-01-02T15:04:05Z07:00 162 // 2017-06-25T17:46:57.45706582-07:00 163 // 2006-01-02T15:04:05.999999999Z07:00 164 // 2006-01-02T15:04:05+0000 165 // 2012-08-03 18:31:59.257000000 166 // 2014-04-26 17:24:37.3186369 167 // 2017-01-27 00:07:31.945167 168 // 2016-03-14 00:00:00.000 169 // 2014-05-11 08:20:13,787 170 // 2017-07-19 03:21:51+00:00 171 // 2006-01-02 172 // 2013-04-01 22:43:22 173 // 2014-04-26 05:24:37 PM 174 // 2013-Feb-03 175 switch { 176 case r == ' ': 177 state = stateDigitDashWs 178 case r == 'T': 179 state = stateDigitDashT 180 default: 181 if unicode.IsLetter(r) { 182 state = stateDigitDashAlpha 183 break iterRunes 184 } 185 } 186 case stateDigitDashWs: 187 // 2013-04-01 22:43:22 188 // 2014-05-11 08:20:13,787 189 // stateDigitDashWsWs 190 // 2014-04-26 05:24:37 PM 191 // 2014-12-16 06:20:00 UTC 192 // 2015-02-18 00:12:00 +0000 UTC 193 // 2006-01-02 15:04:05 -0700 194 // 2006-01-02 15:04:05 -07:00 195 // stateDigitDashWsOffset 196 // 2017-07-19 03:21:51+00:00 197 // stateDigitDashWsPeriod 198 // 2014-04-26 17:24:37.3186369 199 // 2017-01-27 00:07:31.945167 200 // 2012-08-03 18:31:59.257000000 201 // 2016-03-14 00:00:00.000 202 // stateDigitDashWsPeriodOffset 203 // 2017-01-27 00:07:31.945167 +0000 204 // 2016-03-14 00:00:00.000 +0000 205 // stateDigitDashWsPeriodOffsetAlpha 206 // 2017-01-27 00:07:31.945167 +0000 UTC 207 // 2016-03-14 00:00:00.000 +0000 UTC 208 // stateDigitDashWsPeriodAlpha 209 // 2014-12-16 06:20:00.000 UTC 210 switch r { 211 case ',': 212 if len(datestr) == len("2014-05-11 08:20:13,787") { 213 // go doesn't seem to parse this one natively? or did i miss it? 214 t, err := parse("2006-01-02 03:04:05", datestr[:i], loc) 215 if err == nil { 216 ms, err := strconv.Atoi(datestr[i+1:]) 217 if err == nil { 218 return time.Unix(0, t.UnixNano()+int64(ms)*1e6), nil 219 } 220 } 221 return t, err 222 } 223 case '-', '+': 224 state = stateDigitDashWsOffset 225 case '.': 226 state = stateDigitDashWsPeriod 227 case ' ': 228 state = stateDigitDashWsWs 229 } 230 231 case stateDigitDashWsWs: 232 // stateDigitDashWsWsAlpha 233 // 2014-12-16 06:20:00 UTC 234 // stateDigitDashWsWsAMPMMaybe 235 // 2014-04-26 05:24:37 PM 236 // stateDigitDashWsWsOffset 237 // 2006-01-02 15:04:05 -0700 238 // stateDigitDashWsWsOffsetColon 239 // 2006-01-02 15:04:05 -07:00 240 // stateDigitDashWsWsOffsetColonAlpha 241 // 2015-02-18 00:12:00 +00:00 UTC 242 // stateDigitDashWsWsOffsetAlpha 243 // 2015-02-18 00:12:00 +0000 UTC 244 switch r { 245 case 'A', 'P': 246 state = stateDigitDashWsWsAMPMMaybe 247 case '+', '-': 248 state = stateDigitDashWsWsOffset 249 default: 250 if unicode.IsLetter(r) { 251 // 2014-12-16 06:20:00 UTC 252 state = stateDigitDashWsWsAlpha 253 break iterRunes 254 } 255 } 256 257 case stateDigitDashWsWsAMPMMaybe: 258 if r == 'M' { 259 return parse("2006-01-02 03:04:05 PM", datestr, loc) 260 } 261 state = stateDigitDashWsWsAlpha 262 263 case stateDigitDashWsWsOffset: 264 // stateDigitDashWsWsOffset 265 // 2006-01-02 15:04:05 -0700 266 // stateDigitDashWsWsOffsetColon 267 // 2006-01-02 15:04:05 -07:00 268 // stateDigitDashWsWsOffsetColonAlpha 269 // 2015-02-18 00:12:00 +00:00 UTC 270 // stateDigitDashWsWsOffsetAlpha 271 // 2015-02-18 00:12:00 +0000 UTC 272 if r == ':' { 273 state = stateDigitDashWsWsOffsetColon 274 } else if unicode.IsLetter(r) { 275 // 2015-02-18 00:12:00 +0000 UTC 276 state = stateDigitDashWsWsOffsetAlpha 277 break iterRunes 278 } 279 280 case stateDigitDashWsWsOffsetColon: 281 // stateDigitDashWsWsOffsetColon 282 // 2006-01-02 15:04:05 -07:00 283 // stateDigitDashWsWsOffsetColonAlpha 284 // 2015-02-18 00:12:00 +00:00 UTC 285 if unicode.IsLetter(r) { 286 // 2015-02-18 00:12:00 +00:00 UTC 287 state = stateDigitDashWsWsOffsetColonAlpha 288 break iterRunes 289 } 290 291 case stateDigitDashWsPeriod: 292 // 2014-04-26 17:24:37.3186369 293 // 2017-01-27 00:07:31.945167 294 // 2012-08-03 18:31:59.257000000 295 // 2016-03-14 00:00:00.000 296 // stateDigitDashWsPeriodOffset 297 // 2017-01-27 00:07:31.945167 +0000 298 // 2016-03-14 00:00:00.000 +0000 299 // stateDigitDashWsPeriodOffsetAlpha 300 // 2017-01-27 00:07:31.945167 +0000 UTC 301 // 2016-03-14 00:00:00.000 +0000 UTC 302 // stateDigitDashWsPeriodAlpha 303 // 2014-12-16 06:20:00.000 UTC 304 if unicode.IsLetter(r) { 305 // 2014-12-16 06:20:00.000 UTC 306 state = stateDigitDashWsPeriodAlpha 307 break iterRunes 308 } else if r == '+' || r == '-' { 309 state = stateDigitDashWsPeriodOffset 310 } 311 case stateDigitDashWsPeriodOffset: 312 // 2017-01-27 00:07:31.945167 +0000 313 // 2016-03-14 00:00:00.000 +0000 314 // stateDigitDashWsPeriodOffsetAlpha 315 // 2017-01-27 00:07:31.945167 +0000 UTC 316 // 2016-03-14 00:00:00.000 +0000 UTC 317 if unicode.IsLetter(r) { 318 // 2014-12-16 06:20:00.000 UTC 319 // 2017-01-27 00:07:31.945167 +0000 UTC 320 // 2016-03-14 00:00:00.000 +0000 UTC 321 state = stateDigitDashWsPeriodOffsetAlpha 322 break iterRunes 323 } 324 case stateDigitDashT: // starts digit then dash 02- then T 325 // stateDigitDashT 326 // 2006-01-02T15:04:05 327 // stateDigitDashTZ 328 // 2006-01-02T15:04:05.999999999Z 329 // 2006-01-02T15:04:05.99999999Z 330 // 2006-01-02T15:04:05.9999999Z 331 // 2006-01-02T15:04:05.999999Z 332 // 2006-01-02T15:04:05.99999Z 333 // 2006-01-02T15:04:05.9999Z 334 // 2006-01-02T15:04:05.999Z 335 // 2006-01-02T15:04:05.99Z 336 // 2009-08-12T22:15Z 337 // stateDigitDashTZDigit 338 // 2006-01-02T15:04:05.999999999Z07:00 339 // 2006-01-02T15:04:05Z07:00 340 // With another dash aka time-zone at end 341 // stateDigitDashTOffset 342 // stateDigitDashTOffsetColon 343 // 2017-06-25T17:46:57.45706582-07:00 344 // 2017-06-25T17:46:57+04:00 345 // 2006-01-02T15:04:05+0000 346 switch r { 347 case '-', '+': 348 state = stateDigitDashTOffset 349 case 'Z': 350 state = stateDigitDashTZ 351 } 352 case stateDigitDashTZ: 353 if unicode.IsDigit(r) { 354 state = stateDigitDashTZDigit 355 } 356 case stateDigitDashTOffset: 357 if r == ':' { 358 state = stateDigitDashTOffsetColon 359 } 360 case stateDigitSlash: // starts digit then slash 02/ 361 // 2014/07/10 06:55:38.156283 362 // 03/19/2012 10:11:59 363 // 04/2/2014 03:00:37 364 // 3/1/2012 10:11:59 365 // 4/8/2014 22:05 366 // 3/1/2014 367 // 10/13/2014 368 // 01/02/2006 369 // 1/2/06 370 if unicode.IsDigit(r) || r == '/' { 371 continue 372 } 373 switch r { 374 case ' ': 375 state = stateDigitSlashWS 376 } 377 case stateDigitSlashWS: // starts digit then slash 02/ more digits/slashes then whitespace 378 // 2014/07/10 06:55:38.156283 379 // 03/19/2012 10:11:59 380 // 04/2/2014 03:00:37 381 // 3/1/2012 10:11:59 382 // 4/8/2014 22:05 383 switch r { 384 case ':': 385 state = stateDigitSlashWSColon 386 } 387 case stateDigitSlashWSColon: // starts digit then slash 02/ more digits/slashes then whitespace 388 // 2014/07/10 06:55:38.156283 389 // 03/19/2012 10:11:59 390 // 04/2/2014 03:00:37 391 // 3/1/2012 10:11:59 392 // 4/8/2014 22:05 393 // 3/1/2012 10:11:59 AM 394 switch r { 395 case ':': 396 state = stateDigitSlashWSColonColon 397 case 'A', 'P': 398 state = stateDigitSlashWSColonAMPM 399 } 400 case stateDigitSlashWSColonColon: // starts digit then slash 02/ more digits/slashes then whitespace 401 // 2014/07/10 06:55:38.156283 402 // 03/19/2012 10:11:59 403 // 04/2/2014 03:00:37 404 // 3/1/2012 10:11:59 405 // 4/8/2014 22:05 406 // 3/1/2012 10:11:59 AM 407 switch r { 408 case 'A', 'P': 409 state = stateDigitSlashWSColonColonAMPM 410 } 411 case stateDigitAlpha: 412 // 12 Feb 2006, 19:17 413 // 12 Feb 2006, 19:17:22 414 switch { 415 case len(datestr) == len("02 Jan 2006, 15:04"): 416 return parse("02 Jan 2006, 15:04", datestr, loc) 417 case len(datestr) == len("02 Jan 2006, 15:04:05"): 418 return parse("02 Jan 2006, 15:04:05", datestr, loc) 419 case len(datestr) == len("2006年01月02日"): 420 return parse("2006年01月02日", datestr, loc) 421 case len(datestr) == len("2006年01月02日 15:04"): 422 return parse("2006年01月02日 15:04", datestr, loc) 423 case strings.Contains(datestr, "ago"): 424 state = stateHowLongAgo 425 } 426 case stateAlpha: // starts alpha 427 // stateAlphaWS 428 // Mon Jan _2 15:04:05 2006 429 // Mon Jan _2 15:04:05 MST 2006 430 // Mon Jan 02 15:04:05 -0700 2006 431 // Mon Aug 10 15:44:11 UTC+0100 2015 432 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 433 // stateAlphaWSDigitComma 434 // May 8, 2009 5:57:51 PM 435 // 436 // stateWeekdayComma 437 // Monday, 02-Jan-06 15:04:05 MST 438 // stateWeekdayCommaOffset 439 // Monday, 02 Jan 2006 15:04:05 -0700 440 // Monday, 02 Jan 2006 15:04:05 +0100 441 // stateWeekdayAbbrevComma 442 // Mon, 02-Jan-06 15:04:05 MST 443 // Mon, 02 Jan 2006 15:04:05 MST 444 // stateWeekdayAbbrevCommaOffset 445 // Mon, 02 Jan 2006 15:04:05 -0700 446 // Thu, 13 Jul 2017 08:58:40 +0100 447 // stateWeekdayAbbrevCommaOffsetZone 448 // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) 449 switch { 450 case unicode.IsLetter(r): 451 continue 452 case r == ' ': 453 state = stateAlphaWS 454 case r == ',': 455 if i == 3 { 456 state = stateWeekdayAbbrevComma 457 } else { 458 state = stateWeekdayComma 459 } 460 } 461 case stateWeekdayComma: // Starts alpha then comma 462 // Mon, 02-Jan-06 15:04:05 MST 463 // Mon, 02 Jan 2006 15:04:05 MST 464 // stateWeekdayCommaOffset 465 // Monday, 02 Jan 2006 15:04:05 -0700 466 // Monday, 02 Jan 2006 15:04:05 +0100 467 switch { 468 case r == '-': 469 if i < 15 { 470 return parse("Monday, 02-Jan-06 15:04:05 MST", datestr, loc) 471 } 472 state = stateWeekdayCommaOffset 473 case r == '+': 474 state = stateWeekdayCommaOffset 475 } 476 case stateWeekdayAbbrevComma: // Starts alpha then comma 477 // Mon, 02-Jan-06 15:04:05 MST 478 // Mon, 02 Jan 2006 15:04:05 MST 479 // stateWeekdayAbbrevCommaOffset 480 // Mon, 02 Jan 2006 15:04:05 -0700 481 // Thu, 13 Jul 2017 08:58:40 +0100 482 // stateWeekdayAbbrevCommaOffsetZone 483 // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) 484 switch { 485 case r == '-': 486 if i < 15 { 487 return parse("Mon, 02-Jan-06 15:04:05 MST", datestr, loc) 488 } 489 state = stateWeekdayAbbrevCommaOffset 490 case r == '+': 491 state = stateWeekdayAbbrevCommaOffset 492 } 493 494 case stateWeekdayAbbrevCommaOffset: 495 // stateWeekdayAbbrevCommaOffset 496 // Mon, 02 Jan 2006 15:04:05 -0700 497 // Thu, 13 Jul 2017 08:58:40 +0100 498 // stateWeekdayAbbrevCommaOffsetZone 499 // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) 500 if r == '(' { 501 state = stateWeekdayAbbrevCommaOffsetZone 502 } 503 504 case stateAlphaWS: // Starts alpha then whitespace 505 // Mon Jan _2 15:04:05 2006 506 // Mon Jan _2 15:04:05 MST 2006 507 // Mon Jan 02 15:04:05 -0700 2006 508 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 509 // Mon Aug 10 15:44:11 UTC+0100 2015 510 switch { 511 case unicode.IsLetter(r): 512 state = stateAlphaWSAlpha 513 case unicode.IsDigit(r): 514 state = stateAlphaWSDigitComma 515 } 516 517 case stateAlphaWSDigitComma: // Starts Alpha, whitespace, digit, comma 518 // May 8, 2009 5:57:51 PM 519 // May 8, 2009 520 if len(datestr) == len("May 8, 2009") { 521 return parse("Jan 2, 2006", datestr, loc) 522 } 523 return parse("Jan 2, 2006 3:04:05 PM", datestr, loc) 524 525 case stateAlphaWSAlpha: // Alpha, whitespace, alpha 526 // Mon Jan _2 15:04:05 2006 527 // Mon Jan 02 15:04:05 -0700 2006 528 // Mon Jan _2 15:04:05 MST 2006 529 // Mon Aug 10 15:44:11 UTC+0100 2015 530 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 531 if r == ':' { 532 state = stateAlphaWSAlphaColon 533 } 534 case stateAlphaWSAlphaColon: // Alpha, whitespace, alpha, : 535 // Mon Jan _2 15:04:05 2006 536 // Mon Jan 02 15:04:05 -0700 2006 537 // Mon Jan _2 15:04:05 MST 2006 538 // Mon Aug 10 15:44:11 UTC+0100 2015 539 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 540 if unicode.IsLetter(r) { 541 state = stateAlphaWSAlphaColonAlpha 542 } else if r == '-' || r == '+' { 543 state = stateAlphaWSAlphaColonOffset 544 } 545 case stateAlphaWSAlphaColonAlpha: // Alpha, whitespace, alpha, :, alpha 546 // Mon Jan _2 15:04:05 MST 2006 547 // Mon Aug 10 15:44:11 UTC+0100 2015 548 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 549 if r == '+' { 550 state = stateAlphaWSAlphaColonAlphaOffset 551 } 552 case stateAlphaWSAlphaColonAlphaOffset: // Alpha, whitespace, alpha, : , alpha, offset, ? 553 // Mon Aug 10 15:44:11 UTC+0100 2015 554 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 555 if unicode.IsLetter(r) { 556 state = stateAlphaWSAlphaColonAlphaOffsetAlpha 557 } 558 default: 559 break iterRunes 560 } 561 } 562 563 switch state { 564 case stateDigit: 565 // unixy timestamps ish 566 // 1499979655583057426 nanoseconds 567 // 1499979795437000 micro-seconds 568 // 1499979795437 milliseconds 569 // 1384216367189 570 // 1332151919 seconds 571 // 20140601 yyyymmdd 572 // 2014 yyyy 573 t := time.Time{} 574 if len(datestr) > len("1499979795437000") { 575 if nanoSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 576 t = time.Unix(0, nanoSecs) 577 } 578 } else if len(datestr) > len("1499979795437") { 579 if microSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 580 t = time.Unix(0, microSecs*1000) 581 } 582 } else if len(datestr) > len("1332151919") { 583 if miliSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 584 t = time.Unix(0, miliSecs*1000*1000) 585 } 586 } else if len(datestr) == len("20140601") { 587 return parse("20060102", datestr, loc) 588 } else if len(datestr) == len("2014") { 589 return parse("2006", datestr, loc) 590 } 591 if t.IsZero() { 592 if secs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 593 if secs < 0 { 594 // Now, for unix-seconds we aren't going to guess a lot 595 // nothing before unix-epoch 596 } else { 597 t = time.Unix(secs, 0) 598 } 599 } 600 } 601 if !t.IsZero() { 602 if loc == nil { 603 return t, nil 604 } 605 return t.In(loc), nil 606 } 607 608 case stateDigitDash: // starts digit then dash 02- 609 // 2006-01-02 610 // 2006-01 611 if len(datestr) == len("2014-04-26") { 612 return parse("2006-01-02", datestr, loc) 613 } else if len(datestr) == len("2014-04") { 614 return parse("2006-01", datestr, loc) 615 } 616 case stateDigitDashAlpha: 617 // 2013-Feb-03 618 return parse("2006-Jan-02", datestr, loc) 619 620 case stateDigitDashTOffset: 621 // 2006-01-02T15:04:05+0000 622 return parse("2006-01-02T15:04:05-0700", datestr, loc) 623 624 case stateDigitDashTOffsetColon: 625 // With another +/- time-zone at end 626 // 2006-01-02T15:04:05.999999999+07:00 627 // 2006-01-02T15:04:05.999999999-07:00 628 // 2006-01-02T15:04:05.999999+07:00 629 // 2006-01-02T15:04:05.999999-07:00 630 // 2006-01-02T15:04:05.999+07:00 631 // 2006-01-02T15:04:05.999-07:00 632 // 2006-01-02T15:04:05+07:00 633 // 2006-01-02T15:04:05-07:00 634 return parse("2006-01-02T15:04:05-07:00", datestr, loc) 635 636 case stateDigitDashT: // starts digit then dash 02- then T 637 // 2006-01-02T15:04:05.999999 638 // 2006-01-02T15:04:05.999999 639 return parse("2006-01-02T15:04:05", datestr, loc) 640 641 case stateDigitDashTZDigit: 642 // With a time-zone at end after Z 643 // 2006-01-02T15:04:05.999999999Z07:00 644 // 2006-01-02T15:04:05Z07:00 645 // RFC3339 = "2006-01-02T15:04:05Z07:00" 646 // RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" 647 return time.Time{}, fmt.Errorf("RFC339 Dates may not contain both Z & Offset for %q see https://github.com/golang/go/issues/5294", datestr) 648 649 case stateDigitDashTZ: // starts digit then dash 02- then T Then Z 650 // 2006-01-02T15:04:05.999999999Z 651 // 2006-01-02T15:04:05.99999999Z 652 // 2006-01-02T15:04:05.9999999Z 653 // 2006-01-02T15:04:05.999999Z 654 // 2006-01-02T15:04:05.99999Z 655 // 2006-01-02T15:04:05.9999Z 656 // 2006-01-02T15:04:05.999Z 657 // 2006-01-02T15:04:05.99Z 658 // 2009-08-12T22:15Z -- No seconds/milliseconds 659 switch len(datestr) { 660 case len("2009-08-12T22:15Z"): 661 return parse("2006-01-02T15:04Z", datestr, loc) 662 default: 663 return parse("2006-01-02T15:04:05Z", datestr, loc) 664 } 665 case stateDigitDashWs: // starts digit then dash 02- then whitespace 1 << 2 << 5 + 3 666 // 2013-04-01 22:43:22 667 return parse("2006-01-02 15:04:05", datestr, loc) 668 669 case stateDigitDashWsWsOffset: 670 // 2006-01-02 15:04:05 -0700 671 return parse("2006-01-02 15:04:05 -0700", datestr, loc) 672 673 case stateDigitDashWsWsOffsetColon: 674 // 2006-01-02 15:04:05 -07:00 675 return parse("2006-01-02 15:04:05 -07:00", datestr, loc) 676 677 case stateDigitDashWsWsOffsetAlpha: 678 // 2015-02-18 00:12:00 +0000 UTC 679 t, err := parse("2006-01-02 15:04:05 -0700 UTC", datestr, loc) 680 if err == nil { 681 return t, nil 682 } 683 return parse("2006-01-02 15:04:05 +0000 GMT", datestr, loc) 684 685 case stateDigitDashWsWsOffsetColonAlpha: 686 // 2015-02-18 00:12:00 +00:00 UTC 687 return parse("2006-01-02 15:04:05 -07:00 UTC", datestr, loc) 688 689 case stateDigitDashWsOffset: 690 // 2017-07-19 03:21:51+00:00 691 return parse("2006-01-02 15:04:05-07:00", datestr, loc) 692 693 case stateDigitDashWsWsAlpha: 694 // 2014-12-16 06:20:00 UTC 695 t, err := parse("2006-01-02 15:04:05 UTC", datestr, loc) 696 if err == nil { 697 return t, nil 698 } 699 t, err = parse("2006-01-02 15:04:05 GMT", datestr, loc) 700 if err == nil { 701 return t, nil 702 } 703 if len(datestr) > len("2006-01-02 03:04:05") { 704 t, err = parse("2006-01-02 03:04:05", datestr[:len("2006-01-02 03:04:05")], loc) 705 if err == nil { 706 return t, nil 707 } 708 } 709 710 case stateDigitDashWsPeriod: 711 // 2012-08-03 18:31:59.257000000 712 // 2014-04-26 17:24:37.3186369 713 // 2017-01-27 00:07:31.945167 714 // 2016-03-14 00:00:00.000 715 return parse("2006-01-02 15:04:05", datestr, loc) 716 717 case stateDigitDashWsPeriodAlpha: 718 // 2012-08-03 18:31:59.257000000 UTC 719 // 2014-04-26 17:24:37.3186369 UTC 720 // 2017-01-27 00:07:31.945167 UTC 721 // 2016-03-14 00:00:00.000 UTC 722 return parse("2006-01-02 15:04:05 UTC", datestr, loc) 723 724 case stateDigitDashWsPeriodOffset: 725 // 2012-08-03 18:31:59.257000000 +0000 726 // 2014-04-26 17:24:37.3186369 +0000 727 // 2017-01-27 00:07:31.945167 +0000 728 // 2016-03-14 00:00:00.000 +0000 729 return parse("2006-01-02 15:04:05 -0700", datestr, loc) 730 731 case stateDigitDashWsPeriodOffsetAlpha: 732 // 2012-08-03 18:31:59.257000000 +0000 UTC 733 // 2014-04-26 17:24:37.3186369 +0000 UTC 734 // 2017-01-27 00:07:31.945167 +0000 UTC 735 // 2016-03-14 00:00:00.000 +0000 UTC 736 return parse("2006-01-02 15:04:05 -0700 UTC", datestr, loc) 737 738 case stateAlphaWSAlphaColon: 739 // Mon Jan _2 15:04:05 2006 740 return parse(time.ANSIC, datestr, loc) 741 742 case stateAlphaWSAlphaColonOffset: 743 // Mon Jan 02 15:04:05 -0700 2006 744 return parse(time.RubyDate, datestr, loc) 745 746 case stateAlphaWSAlphaColonAlpha: 747 // Mon Jan _2 15:04:05 MST 2006 748 return parse(time.UnixDate, datestr, loc) 749 750 case stateAlphaWSAlphaColonAlphaOffset: 751 // Mon Aug 10 15:44:11 UTC+0100 2015 752 return parse("Mon Jan 02 15:04:05 MST-0700 2006", datestr, loc) 753 754 case stateAlphaWSAlphaColonAlphaOffsetAlpha: 755 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 756 if len(datestr) > len("Mon Jan 02 2006 15:04:05 MST-0700") { 757 // What effing time stamp is this? 758 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 759 dateTmp := datestr[:33] 760 return parse("Mon Jan 02 2006 15:04:05 MST-0700", dateTmp, loc) 761 } 762 case stateDigitSlash: // starts digit then slash 02/ (but nothing else) 763 // 3/1/2014 764 // 10/13/2014 765 // 01/02/2006 766 // 2014/10/13 767 if firstSlash == 4 { 768 if len(datestr) == len("2006/01/02") { 769 return parse("2006/01/02", datestr, loc) 770 } 771 return parse("2006/1/2", datestr, loc) 772 } 773 for _, parseFormat := range shortDates { 774 if t, err := parse(parseFormat, datestr, loc); err == nil { 775 return t, nil 776 } 777 } 778 779 case stateDigitSlashWSColon: // starts digit then slash 02/ more digits/slashes then whitespace 780 // 4/8/2014 22:05 781 // 04/08/2014 22:05 782 // 2014/4/8 22:05 783 // 2014/04/08 22:05 784 785 if firstSlash == 4 { 786 for _, layout := range []string{"2006/01/02 15:04", "2006/1/2 15:04", "2006/01/2 15:04", "2006/1/02 15:04"} { 787 if t, err := parse(layout, datestr, loc); err == nil { 788 return t, nil 789 } 790 } 791 } else { 792 for _, layout := range []string{"01/02/2006 15:04", "01/2/2006 15:04", "1/02/2006 15:04", "1/2/2006 15:04"} { 793 if t, err := parse(layout, datestr, loc); err == nil { 794 return t, nil 795 } 796 } 797 } 798 799 case stateDigitSlashWSColonAMPM: // starts digit then slash 02/ more digits/slashes then whitespace 800 // 4/8/2014 22:05 PM 801 // 04/08/2014 22:05 PM 802 // 04/08/2014 1:05 PM 803 // 2014/4/8 22:05 PM 804 // 2014/04/08 22:05 PM 805 806 if firstSlash == 4 { 807 for _, layout := range []string{"2006/01/02 03:04 PM", "2006/01/2 03:04 PM", "2006/1/02 03:04 PM", "2006/1/2 03:04 PM", 808 "2006/01/02 3:04 PM", "2006/01/2 3:04 PM", "2006/1/02 3:04 PM", "2006/1/2 3:04 PM"} { 809 if t, err := parse(layout, datestr, loc); err == nil { 810 return t, nil 811 } 812 } 813 } else { 814 for _, layout := range []string{"01/02/2006 03:04 PM", "01/2/2006 03:04 PM", "1/02/2006 03:04 PM", "1/2/2006 03:04 PM", 815 "01/02/2006 3:04 PM", "01/2/2006 3:04 PM", "1/02/2006 3:04 PM", "1/2/2006 3:04 PM"} { 816 if t, err := parse(layout, datestr, loc); err == nil { 817 return t, nil 818 } 819 820 } 821 } 822 823 case stateDigitSlashWSColonColon: // starts digit then slash 02/ more digits/slashes then whitespace double colons 824 // 2014/07/10 06:55:38.156283 825 // 03/19/2012 10:11:59 826 // 3/1/2012 10:11:59 827 // 03/1/2012 10:11:59 828 // 3/01/2012 10:11:59 829 if firstSlash == 4 { 830 for _, layout := range []string{"2006/01/02 15:04:05", "2006/1/02 15:04:05", "2006/01/2 15:04:05", "2006/1/2 15:04:05"} { 831 if t, err := parse(layout, datestr, loc); err == nil { 832 return t, nil 833 } 834 } 835 } else { 836 for _, layout := range []string{"01/02/2006 15:04:05", "1/02/2006 15:04:05", "01/2/2006 15:04:05", "1/2/2006 15:04:05"} { 837 if t, err := parse(layout, datestr, loc); err == nil { 838 return t, nil 839 } 840 } 841 } 842 843 case stateDigitSlashWSColonColonAMPM: // starts digit then slash 02/ more digits/slashes then whitespace double colons 844 // 2014/07/10 06:55:38.156283 PM 845 // 03/19/2012 10:11:59 PM 846 // 3/1/2012 10:11:59 PM 847 // 03/1/2012 10:11:59 PM 848 // 3/01/2012 10:11:59 PM 849 850 if firstSlash == 4 { 851 for _, layout := range []string{"2006/01/02 03:04:05 PM", "2006/1/02 03:04:05 PM", "2006/01/2 03:04:05 PM", "2006/1/2 03:04:05 PM", 852 "2006/01/02 3:04:05 PM", "2006/1/02 3:04:05 PM", "2006/01/2 3:04:05 PM", "2006/1/2 3:04:05 PM"} { 853 if t, err := parse(layout, datestr, loc); err == nil { 854 return t, nil 855 } 856 } 857 } else { 858 for _, layout := range []string{"01/02/2006 03:04:05 PM", "1/02/2006 03:04:05 PM", "01/2/2006 03:04:05 PM", "1/2/2006 03:04:05 PM"} { 859 if t, err := parse(layout, datestr, loc); err == nil { 860 return t, nil 861 } 862 } 863 } 864 865 case stateWeekdayCommaOffset: 866 // Monday, 02 Jan 2006 15:04:05 -0700 867 // Monday, 02 Jan 2006 15:04:05 +0100 868 return parse("Monday, 02 Jan 2006 15:04:05 -0700", datestr, loc) 869 case stateWeekdayAbbrevComma: // Starts alpha then comma 870 // Mon, 02-Jan-06 15:04:05 MST 871 // Mon, 02 Jan 2006 15:04:05 MST 872 return parse("Mon, 02 Jan 2006 15:04:05 MST", datestr, loc) 873 case stateWeekdayAbbrevCommaOffset: 874 // Mon, 02 Jan 2006 15:04:05 -0700 875 // Thu, 13 Jul 2017 08:58:40 +0100 876 // RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone 877 return parse("Mon, 02 Jan 2006 15:04:05 -0700", datestr, loc) 878 case stateWeekdayAbbrevCommaOffsetZone: 879 // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) 880 return parse("Mon, 02 Jan 2006 15:04:05 -0700 (CEST)", datestr, loc) 881 case stateHowLongAgo: 882 // 1 minutes ago 883 // 1 hours ago 884 // 1 day ago 885 switch len(datestr) { 886 case len("1 minutes ago"), len("10 minutes ago"), len("100 minutes ago"): 887 return agoTime(datestr, time.Minute) 888 case len("1 hours ago"), len("10 hours ago"): 889 return agoTime(datestr, time.Hour) 890 case len("1 day ago"), len("10 day ago"): 891 return agoTime(datestr, Day) 892 } 893 } 894 895 return time.Time{}, fmt.Errorf("Could not find date format for %s", datestr) 896 } 897 898 func agoTime(datestr string, d time.Duration) (time.Time, error) { 899 dstrs := strings.Split(datestr, " ") 900 m, err := strconv.Atoi(dstrs[0]) 901 if err != nil { 902 return time.Time{}, err 903 } 904 return time.Now().Add(-d * time.Duration(m)), nil 905 }