github.com/go-board/x-go@v0.1.2-0.20220610024734-db1323f6cb15/xtime/parse.go (about) 1 // Package dateparse parses date-strings without knowing the format 2 // in advance, using a fast lex based approach to eliminate shotgun 3 // attempts. It leans towards US style dates when there is a conflict. 4 5 package xtime 6 7 import ( 8 "fmt" 9 "strconv" 10 "strings" 11 "time" 12 "unicode" 13 "unicode/utf8" 14 ) 15 16 var months = []string{ 17 "january", 18 "february", 19 "march", 20 "april", 21 "may", 22 "june", 23 "july", 24 "august", 25 "september", 26 "october", 27 "november", 28 "december", 29 } 30 31 type dateState uint8 32 type timeState uint8 33 34 const ( 35 dateStart dateState = iota // 0 36 dateDigit 37 dateYearDash 38 dateYearDashAlphaDash 39 dateYearDashDash 40 dateYearDashDashWs // 5 41 dateYearDashDashT 42 dateDigitDash 43 dateDigitDashAlpha 44 dateDigitDashAlphaDash 45 dateDigitDot // 10 46 dateDigitDotDot 47 dateDigitSlash 48 dateDigitChineseYear 49 dateDigitChineseYearWs 50 dateDigitWs // 15 51 dateDigitWsMoYear 52 dateDigitWsMolong 53 dateAlpha 54 dateAlphaWs 55 dateAlphaWsDigit // 20 56 dateAlphaWsDigitMore 57 dateAlphaWsDigitMoreWs 58 dateAlphaWsDigitMoreWsYear 59 dateAlphaWsMonth 60 dateAlphaWsMonthMore 61 dateAlphaWsMonthSuffix 62 dateAlphaWsMore 63 dateAlphaWsAtTime 64 dateAlphaWsAlpha 65 dateAlphaWsAlphaYearmaybe 66 dateAlphaPeriodWsDigit 67 dateWeekdayComma 68 dateWeekdayAbbrevComma 69 ) 70 const ( 71 // Time state 72 timeIgnore timeState = iota // 0 73 timeStart 74 timeWs 75 timeWsAlpha 76 timeWsAlphaWs 77 timeWsAlphaZoneOffset // 5 78 timeWsAlphaZoneOffsetWs 79 timeWsAlphaZoneOffsetWsYear 80 timeWsAlphaZoneOffsetWsExtra 81 timeWsAMPMMaybe 82 timeWsAMPM // 10 83 timeWsOffset 84 timeWsOffsetWs // 12 85 timeWsOffsetColonAlpha 86 timeWsOffsetColon 87 timeWsYear // 15 88 timeOffset 89 timeOffsetColon 90 timeAlpha 91 timePeriod 92 timePeriodOffset // 20 93 timePeriodOffsetColon 94 timePeriodOffsetColonWs 95 timePeriodWs 96 timePeriodWsAlpha 97 timePeriodWsOffset // 25 98 timePeriodWsOffsetWs 99 timePeriodWsOffsetWsAlpha 100 timePeriodWsOffsetColon 101 timePeriodWsOffsetColonAlpha 102 timeZ 103 timeZDigit 104 ) 105 106 var ( 107 // ErrAmbiguousMMDD for date formats such as 04/02/2014 the mm/dd vs dd/mm are 108 // ambiguous, so it is an error for strict parse rules. 109 ErrAmbiguousMMDD = fmt.Errorf("err: this date has ambiguous mm/dd vs dd/mm type format") 110 ) 111 112 func unknownErr(datestr string) error { 113 return fmt.Errorf("err: could not find format for %q", datestr) 114 } 115 116 // ParseAny parse an unknown date format, detect the layout. 117 // Normal parse. Equivalent Timezone rules as time.Parse(). 118 // NOTE: please see readme on mmdd vs ddmm ambiguous dates. 119 func ParseAny(datestr string) (time.Time, error) { 120 p, err := parseTime(datestr, nil) 121 if err != nil { 122 return time.Time{}, err 123 } 124 return p.parse() 125 } 126 127 // ParseIn with Location, equivalent to time.ParseInLocation() timezone/offset 128 // rules. Using location arg, if timezone/offset info exists in the 129 // datestring, it uses the given location rules for any zone interpretation. 130 // That is, MST means one thing when using America/Denver and something else 131 // in other locations. 132 func ParseIn(datestr string, loc *time.Location) (time.Time, error) { 133 p, err := parseTime(datestr, loc) 134 if err != nil { 135 return time.Time{}, err 136 } 137 return p.parse() 138 } 139 140 // ParseLocal Given an unknown date format, detect the layout, 141 // using time.Local, parse. 142 // 143 // Set Location to time.Local. Same as ParseIn Location but lazily uses 144 // the global time.Local variable for Location argument. 145 // 146 // denverLoc, _ := time.LoadLocation("America/Denver") 147 // time.Local = denverLoc 148 // 149 // t, err := dateparse.ParseLocal("3/1/2014") 150 // 151 // Equivalent to: 152 // 153 // t, err := dateparse.ParseIn("3/1/2014", denverLoc) 154 // 155 func ParseLocal(datestr string) (time.Time, error) { 156 p, err := parseTime(datestr, time.Local) 157 if err != nil { 158 return time.Time{}, err 159 } 160 return p.parse() 161 } 162 163 // MustParse parse a date, and panic if it can't be parsed. Used for testing. 164 // Not recommended for most use-cases. 165 func MustParse(datestr string) time.Time { 166 p, err := parseTime(datestr, nil) 167 if err != nil { 168 panic(err.Error()) 169 } 170 t, err := p.parse() 171 if err != nil { 172 panic(err.Error()) 173 } 174 return t 175 } 176 177 // ParseFormat parse's an unknown date-time string and returns a layout 178 // string that can parse this (and exact same format) other date-time strings. 179 // 180 // layout, err := dateparse.ParseFormat("2013-02-01 00:00:00") 181 // // layout = "2006-01-02 15:04:05" 182 // 183 func ParseFormat(datestr string) (string, error) { 184 p, err := parseTime(datestr, nil) 185 if err != nil { 186 return "", err 187 } 188 _, err = p.parse() 189 if err != nil { 190 return "", err 191 } 192 return string(p.format), nil 193 } 194 195 // ParseStrict parse an unknown date format. IF the date is ambigous 196 // mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc 197 func ParseStrict(datestr string) (time.Time, error) { 198 p, err := parseTime(datestr, nil) 199 if err != nil { 200 return time.Time{}, err 201 } 202 if p.ambiguousMD { 203 return time.Time{}, ErrAmbiguousMMDD 204 } 205 return p.parse() 206 } 207 208 func parseTime(datestr string, loc *time.Location) (*parser, error) { 209 210 p := newParser(datestr, loc) 211 i := 0 212 213 // General strategy is to read rune by rune through the date looking for 214 // certain hints of what type of date we are dealing with. 215 // Hopefully we only need to read about 5 or 6 bytes before 216 // we figure it out and then attempt a parse 217 iterRunes: 218 for ; i < len(datestr); i++ { 219 //r := rune(datestr[i]) 220 r, bytesConsumed := utf8.DecodeRuneInString(datestr[i:]) 221 if bytesConsumed > 1 { 222 i += (bytesConsumed - 1) 223 } 224 225 //gou.Debugf("i=%d r=%s state=%d %s", i, string(r), p.stateDate, datestr) 226 switch p.stateDate { 227 case dateStart: 228 if unicode.IsDigit(r) { 229 p.stateDate = dateDigit 230 } else if unicode.IsLetter(r) { 231 p.stateDate = dateAlpha 232 } else { 233 return nil, unknownErr(datestr) 234 } 235 case dateDigit: 236 237 switch r { 238 case '-', '\u2212': 239 // 2006-01-02 240 // 2013-Feb-03 241 // 13-Feb-03 242 // 29-Jun-2016 243 if i == 4 { 244 p.stateDate = dateYearDash 245 p.yeari = 0 246 p.yearlen = i 247 p.moi = i + 1 248 p.set(0, "2006") 249 } else { 250 p.stateDate = dateDigitDash 251 } 252 case '/': 253 // 03/31/2005 254 // 2014/02/24 255 p.stateDate = dateDigitSlash 256 if i == 4 { 257 p.yearlen = i 258 p.moi = i + 1 259 p.setYear() 260 } else { 261 p.ambiguousMD = true 262 if p.preferMonthFirst { 263 if p.molen == 0 { 264 p.molen = i 265 p.setMonth() 266 p.dayi = i + 1 267 } 268 } 269 } 270 271 case '.': 272 // 3.31.2014 273 // 08.21.71 274 // 2014.05 275 p.stateDate = dateDigitDot 276 if i == 4 { 277 p.yearlen = i 278 p.moi = i + 1 279 p.setYear() 280 } else { 281 p.ambiguousMD = true 282 p.moi = 0 283 p.molen = i 284 p.setMonth() 285 p.dayi = i + 1 286 } 287 288 case ' ': 289 // 18 January 2018 290 // 8 January 2018 291 // 8 jan 2018 292 // 02 Jan 2018 23:59 293 // 02 Jan 2018 23:59:34 294 // 12 Feb 2006, 19:17 295 // 12 Feb 2006, 19:17:22 296 p.stateDate = dateDigitWs 297 p.dayi = 0 298 p.daylen = i 299 case '年': 300 // Chinese Year 301 p.stateDate = dateDigitChineseYear 302 case ',': 303 return nil, unknownErr(datestr) 304 default: 305 continue 306 } 307 p.part1Len = i 308 309 case dateYearDash: 310 // dateYearDashDashT 311 // 2006-01-02T15:04:05Z07:00 312 // dateYearDashDashWs 313 // 2013-04-01 22:43:22 314 // dateYearDashAlphaDash 315 // 2013-Feb-03 316 switch r { 317 case '-': 318 p.molen = i - p.moi 319 p.dayi = i + 1 320 p.stateDate = dateYearDashDash 321 p.setMonth() 322 default: 323 if unicode.IsLetter(r) { 324 p.stateDate = dateYearDashAlphaDash 325 } 326 } 327 328 case dateYearDashDash: 329 // dateYearDashDashT 330 // 2006-01-02T15:04:05Z07:00 331 // dateYearDashDashWs 332 // 2013-04-01 22:43:22 333 switch r { 334 case ' ': 335 p.daylen = i - p.dayi 336 p.stateDate = dateYearDashDashWs 337 p.stateTime = timeStart 338 p.setDay() 339 break iterRunes 340 case 'T': 341 p.daylen = i - p.dayi 342 p.stateDate = dateYearDashDashT 343 p.stateTime = timeStart 344 p.setDay() 345 break iterRunes 346 } 347 case dateYearDashAlphaDash: 348 // 2013-Feb-03 349 switch r { 350 case '-': 351 p.molen = i - p.moi 352 p.set(p.moi, "Jan") 353 p.dayi = i + 1 354 } 355 case dateDigitDash: 356 // 13-Feb-03 357 // 29-Jun-2016 358 if unicode.IsLetter(r) { 359 p.stateDate = dateDigitDashAlpha 360 p.moi = i 361 } else { 362 return nil, unknownErr(datestr) 363 } 364 case dateDigitDashAlpha: 365 // 13-Feb-03 366 // 28-Feb-03 367 // 29-Jun-2016 368 switch r { 369 case '-': 370 p.molen = i - p.moi 371 p.set(p.moi, "Jan") 372 p.yeari = i + 1 373 p.stateDate = dateDigitDashAlphaDash 374 } 375 376 case dateDigitDashAlphaDash: 377 // 13-Feb-03 ambiguous 378 // 28-Feb-03 ambiguous 379 // 29-Jun-2016 380 switch r { 381 case ' ': 382 // we need to find if this was 4 digits, aka year 383 // or 2 digits which makes it ambiguous year/day 384 length := i - (p.moi + p.molen + 1) 385 if length == 4 { 386 p.yearlen = 4 387 p.set(p.yeari, "2006") 388 // We now also know that part1 was the day 389 p.dayi = 0 390 p.daylen = p.part1Len 391 p.setDay() 392 } else if length == 2 { 393 // We have no idea if this is 394 // yy-mon-dd OR dd-mon-yy 395 // 396 // We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption 397 p.ambiguousMD = true 398 p.yearlen = 2 399 p.set(p.yeari, "06") 400 // We now also know that part1 was the day 401 p.dayi = 0 402 p.daylen = p.part1Len 403 p.setDay() 404 } 405 p.stateTime = timeStart 406 break iterRunes 407 } 408 409 case dateDigitSlash: 410 // 2014/07/10 06:55:38.156283 411 // 03/19/2012 10:11:59 412 // 04/2/2014 03:00:37 413 // 3/1/2012 10:11:59 414 // 4/8/2014 22:05 415 // 3/1/2014 416 // 10/13/2014 417 // 01/02/2006 418 // 1/2/06 419 420 switch r { 421 case ' ': 422 p.stateTime = timeStart 423 if p.yearlen == 0 { 424 p.yearlen = i - p.yeari 425 p.setYear() 426 } else if p.daylen == 0 { 427 p.daylen = i - p.dayi 428 p.setDay() 429 } 430 break iterRunes 431 case '/': 432 if p.yearlen > 0 { 433 // 2014/07/10 06:55:38.156283 434 if p.molen == 0 { 435 p.molen = i - p.moi 436 p.setMonth() 437 p.dayi = i + 1 438 } 439 } else if p.preferMonthFirst { 440 if p.daylen == 0 { 441 p.daylen = i - p.dayi 442 p.setDay() 443 p.yeari = i + 1 444 } 445 } 446 } 447 448 case dateDigitWs: 449 // 18 January 2018 450 // 8 January 2018 451 // 8 jan 2018 452 // 1 jan 18 453 // 02 Jan 2018 23:59 454 // 02 Jan 2018 23:59:34 455 // 12 Feb 2006, 19:17 456 // 12 Feb 2006, 19:17:22 457 switch r { 458 case ' ': 459 p.yeari = i + 1 460 //p.yearlen = 4 461 p.dayi = 0 462 p.daylen = p.part1Len 463 p.setDay() 464 p.stateTime = timeStart 465 if i > p.daylen+len(" Sep") { // November etc 466 // If len greather than space + 3 it must be full month 467 p.stateDate = dateDigitWsMolong 468 } else { 469 // If len=3, the might be Feb or May? Ie ambigous abbreviated but 470 // we can parse may with either. BUT, that means the 471 // format may not be correct? 472 // mo := strings.ToLower(datestr[p.daylen+1 : i]) 473 p.moi = p.daylen + 1 474 p.molen = i - p.moi 475 p.set(p.moi, "Jan") 476 p.stateDate = dateDigitWsMoYear 477 } 478 } 479 480 case dateDigitWsMoYear: 481 // 8 jan 2018 482 // 02 Jan 2018 23:59 483 // 02 Jan 2018 23:59:34 484 // 12 Feb 2006, 19:17 485 // 12 Feb 2006, 19:17:22 486 switch r { 487 case ',': 488 p.yearlen = i - p.yeari 489 p.setYear() 490 i++ 491 break iterRunes 492 case ' ': 493 p.yearlen = i - p.yeari 494 p.setYear() 495 break iterRunes 496 } 497 case dateDigitWsMolong: 498 // 18 January 2018 499 // 8 January 2018 500 501 case dateDigitChineseYear: 502 // dateDigitChineseYear 503 // 2014年04月08日 504 // weekday %Y年%m月%e日 %A %I:%M %p 505 // 2013年07月18日 星期四 10:27 上午 506 if r == ' ' { 507 p.stateDate = dateDigitChineseYearWs 508 break 509 } 510 case dateDigitDot: 511 // This is the 2nd period 512 // 3.31.2014 513 // 08.21.71 514 // 2014.05 515 // 2018.09.30 516 if r == '.' { 517 if p.moi == 0 { 518 // 3.31.2014 519 p.daylen = i - p.dayi 520 p.yeari = i + 1 521 p.setDay() 522 p.stateDate = dateDigitDotDot 523 } else { 524 // 2018.09.30 525 //p.molen = 2 526 p.molen = i - p.moi 527 p.dayi = i + 1 528 p.setMonth() 529 p.stateDate = dateDigitDotDot 530 } 531 } 532 case dateDigitDotDot: 533 // iterate all the way through 534 case dateAlpha: 535 // dateAlphaWS 536 // Mon Jan _2 15:04:05 2006 537 // Mon Jan _2 15:04:05 MST 2006 538 // Mon Jan 02 15:04:05 -0700 2006 539 // Mon Aug 10 15:44:11 UTC+0100 2015 540 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 541 // dateAlphaWSDigit 542 // May 8, 2009 5:57:51 PM 543 // oct 1, 1970 544 // dateAlphaWsMonth 545 // April 8, 2009 546 // dateAlphaWsMore 547 // dateAlphaWsAtTime 548 // January 02, 2006 at 3:04pm MST-07 549 // 550 // dateAlphaPeriodWsDigit 551 // oct. 1, 1970 552 // dateWeekdayComma 553 // Monday, 02 Jan 2006 15:04:05 MST 554 // Monday, 02-Jan-06 15:04:05 MST 555 // Monday, 02 Jan 2006 15:04:05 -0700 556 // Monday, 02 Jan 2006 15:04:05 +0100 557 // dateWeekdayAbbrevComma 558 // Mon, 02 Jan 2006 15:04:05 MST 559 // Mon, 02 Jan 2006 15:04:05 -0700 560 // Thu, 13 Jul 2017 08:58:40 +0100 561 // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) 562 // Mon, 02-Jan-06 15:04:05 MST 563 switch { 564 case r == ' ': 565 // X 566 // April 8, 2009 567 if i > 3 { 568 // Check to see if the alpha is name of month? or Day? 569 month := strings.ToLower(datestr[0:i]) 570 if isMonthFull(month) { 571 p.fullMonth = month 572 // len(" 31, 2018") = 9 573 if len(datestr[i:]) < 10 { 574 // April 8, 2009 575 p.stateDate = dateAlphaWsMonth 576 } else { 577 p.stateDate = dateAlphaWsMore 578 } 579 p.dayi = i + 1 580 break 581 } 582 583 } else { 584 // This is possibly ambiguous? May will parse as either though. 585 // So, it could return in-correct format. 586 // May 05, 2005, 05:05:05 587 // May 05 2005, 05:05:05 588 // Jul 05, 2005, 05:05:05 589 p.stateDate = dateAlphaWs 590 } 591 592 case r == ',': 593 // Mon, 02 Jan 2006 594 // p.moi = 0 595 // p.molen = i 596 if i == 3 { 597 p.stateDate = dateWeekdayAbbrevComma 598 p.set(0, "Mon") 599 } else { 600 p.stateDate = dateWeekdayComma 601 p.skip = i + 2 602 i++ 603 // TODO: lets just make this "skip" as we don't need 604 // the mon, monday, they are all superfelous and not needed 605 // just lay down the skip, no need to fill and then skip 606 } 607 case r == '.': 608 // sept. 28, 2017 609 // jan. 28, 2017 610 p.stateDate = dateAlphaPeriodWsDigit 611 if i == 3 { 612 p.molen = i 613 p.set(0, "Jan") 614 } else if i == 4 { 615 // gross 616 datestr = datestr[0:i-1] + datestr[i:] 617 return parseTime(datestr, loc) 618 } else { 619 return nil, unknownErr(datestr) 620 } 621 } 622 623 case dateAlphaWs: 624 // dateAlphaWsAlpha 625 // Mon Jan _2 15:04:05 2006 626 // Mon Jan _2 15:04:05 MST 2006 627 // Mon Jan 02 15:04:05 -0700 2006 628 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 629 // Mon Aug 10 15:44:11 UTC+0100 2015 630 // dateAlphaWsDigit 631 // May 8, 2009 5:57:51 PM 632 // May 8 2009 5:57:51 PM 633 // oct 1, 1970 634 // oct 7, '70 635 switch { 636 case unicode.IsLetter(r): 637 p.set(0, "Mon") 638 p.stateDate = dateAlphaWsAlpha 639 p.set(i, "Jan") 640 case unicode.IsDigit(r): 641 p.set(0, "Jan") 642 p.stateDate = dateAlphaWsDigit 643 p.dayi = i 644 } 645 646 case dateAlphaWsDigit: 647 // May 8, 2009 5:57:51 PM 648 // May 8 2009 5:57:51 PM 649 // oct 1, 1970 650 // oct 7, '70 651 // oct. 7, 1970 652 if r == ',' { 653 p.daylen = i - p.dayi 654 p.setDay() 655 p.stateDate = dateAlphaWsDigitMore 656 } else if r == ' ' { 657 p.daylen = i - p.dayi 658 p.setDay() 659 p.yeari = i + 1 660 p.stateDate = dateAlphaWsDigitMoreWs 661 } else if unicode.IsLetter(r) { 662 p.stateDate = dateAlphaWsMonthSuffix 663 i-- 664 } 665 case dateAlphaWsDigitMore: 666 // x 667 // May 8, 2009 5:57:51 PM 668 // May 05, 2005, 05:05:05 669 // May 05 2005, 05:05:05 670 // oct 1, 1970 671 // oct 7, '70 672 if r == ' ' { 673 p.yeari = i + 1 674 p.stateDate = dateAlphaWsDigitMoreWs 675 } 676 case dateAlphaWsDigitMoreWs: 677 // x 678 // May 8, 2009 5:57:51 PM 679 // May 05, 2005, 05:05:05 680 // oct 1, 1970 681 // oct 7, '70 682 switch r { 683 case '\'': 684 p.yeari = i + 1 685 case ' ', ',': 686 // x 687 // May 8, 2009 5:57:51 PM 688 // x 689 // May 8, 2009, 5:57:51 PM 690 p.stateDate = dateAlphaWsDigitMoreWsYear 691 p.yearlen = i - p.yeari 692 p.setYear() 693 p.stateTime = timeStart 694 break iterRunes 695 } 696 697 case dateAlphaWsAlpha: 698 // Mon Jan _2 15:04:05 2006 699 // Mon Jan 02 15:04:05 -0700 2006 700 // Mon Jan _2 15:04:05 MST 2006 701 // Mon Aug 10 15:44:11 UTC+0100 2015 702 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 703 if r == ' ' { 704 if p.dayi > 0 { 705 p.daylen = i - p.dayi 706 p.setDay() 707 p.yeari = i + 1 708 p.stateDate = dateAlphaWsAlphaYearmaybe 709 p.stateTime = timeStart 710 } 711 } else if unicode.IsDigit(r) { 712 if p.dayi == 0 { 713 p.dayi = i 714 } 715 } 716 717 case dateAlphaWsAlphaYearmaybe: 718 // x 719 // Mon Jan _2 15:04:05 2006 720 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 721 if r == ':' { 722 i = i - 3 723 p.stateDate = dateAlphaWsAlpha 724 p.yeari = 0 725 break iterRunes 726 } else if r == ' ' { 727 // must be year format, not 15:04 728 p.yearlen = i - p.yeari 729 p.setYear() 730 break iterRunes 731 } 732 733 case dateAlphaWsMonth: 734 // April 8, 2009 735 // April 8 2009 736 switch r { 737 case ' ', ',': 738 // x 739 // June 8, 2009 740 // x 741 // June 8 2009 742 if p.daylen == 0 { 743 p.daylen = i - p.dayi 744 p.setDay() 745 } 746 case 's', 'S', 'r', 'R', 't', 'T', 'n', 'N': 747 // st, rd, nd, st 748 i-- 749 p.stateDate = dateAlphaWsMonthSuffix 750 default: 751 if p.daylen > 0 && p.yeari == 0 { 752 p.yeari = i 753 } 754 } 755 case dateAlphaWsMonthMore: 756 // X 757 // January 02, 2006, 15:04:05 758 // January 02 2006, 15:04:05 759 // January 02, 2006 15:04:05 760 // January 02 2006 15:04:05 761 switch r { 762 case ',': 763 p.yearlen = i - p.yeari 764 p.setYear() 765 p.stateTime = timeStart 766 i++ 767 break iterRunes 768 case ' ': 769 p.yearlen = i - p.yeari 770 p.setYear() 771 p.stateTime = timeStart 772 break iterRunes 773 } 774 case dateAlphaWsMonthSuffix: 775 // x 776 // April 8th, 2009 777 // April 8th 2009 778 switch r { 779 case 't', 'T': 780 if p.nextIs(i, 'h') || p.nextIs(i, 'H') { 781 if len(datestr) > i+2 { 782 return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc) 783 } 784 } 785 case 'n', 'N': 786 if p.nextIs(i, 'd') || p.nextIs(i, 'D') { 787 if len(datestr) > i+2 { 788 return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc) 789 } 790 } 791 case 's', 'S': 792 if p.nextIs(i, 't') || p.nextIs(i, 'T') { 793 if len(datestr) > i+2 { 794 return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc) 795 } 796 } 797 case 'r', 'R': 798 if p.nextIs(i, 'd') || p.nextIs(i, 'D') { 799 if len(datestr) > i+2 { 800 return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc) 801 } 802 } 803 } 804 case dateAlphaWsMore: 805 // January 02, 2006, 15:04:05 806 // January 02 2006, 15:04:05 807 // January 2nd, 2006, 15:04:05 808 // January 2nd 2006, 15:04:05 809 // September 17, 2012 at 5:00pm UTC-05 810 switch { 811 case r == ',': 812 // x 813 // January 02, 2006, 15:04:05 814 if p.nextIs(i, ' ') { 815 p.daylen = i - p.dayi 816 p.setDay() 817 p.yeari = i + 2 818 p.stateDate = dateAlphaWsMonthMore 819 i++ 820 } 821 822 case r == ' ': 823 // x 824 // January 02 2006, 15:04:05 825 p.daylen = i - p.dayi 826 p.setDay() 827 p.yeari = i + 1 828 p.stateDate = dateAlphaWsMonthMore 829 case unicode.IsDigit(r): 830 // XX 831 // January 02, 2006, 15:04:05 832 continue 833 case unicode.IsLetter(r): 834 // X 835 // January 2nd, 2006, 15:04:05 836 p.daylen = i - p.dayi 837 p.setDay() 838 p.stateDate = dateAlphaWsMonthSuffix 839 i-- 840 } 841 842 case dateAlphaPeriodWsDigit: 843 // oct. 7, '70 844 switch { 845 case r == ' ': 846 // continue 847 case unicode.IsDigit(r): 848 p.stateDate = dateAlphaWsDigit 849 p.dayi = i 850 default: 851 return p, unknownErr(datestr) 852 } 853 case dateWeekdayComma: 854 // Monday, 02 Jan 2006 15:04:05 MST 855 // Monday, 02 Jan 2006 15:04:05 -0700 856 // Monday, 02 Jan 2006 15:04:05 +0100 857 // Monday, 02-Jan-06 15:04:05 MST 858 if p.dayi == 0 { 859 p.dayi = i 860 } 861 switch r { 862 case ' ', '-': 863 if p.moi == 0 { 864 p.moi = i + 1 865 p.daylen = i - p.dayi 866 p.setDay() 867 } else if p.yeari == 0 { 868 p.yeari = i + 1 869 p.molen = i - p.moi 870 p.set(p.moi, "Jan") 871 } else { 872 p.stateTime = timeStart 873 break iterRunes 874 } 875 } 876 case dateWeekdayAbbrevComma: 877 // Mon, 02 Jan 2006 15:04:05 MST 878 // Mon, 02 Jan 2006 15:04:05 -0700 879 // Thu, 13 Jul 2017 08:58:40 +0100 880 // Thu, 4 Jan 2018 17:53:36 +0000 881 // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) 882 // Mon, 02-Jan-06 15:04:05 MST 883 switch r { 884 case ' ', '-': 885 if p.dayi == 0 { 886 p.dayi = i + 1 887 } else if p.moi == 0 { 888 p.daylen = i - p.dayi 889 p.setDay() 890 p.moi = i + 1 891 } else if p.yeari == 0 { 892 p.molen = i - p.moi 893 p.set(p.moi, "Jan") 894 p.yeari = i + 1 895 } else { 896 p.yearlen = i - p.yeari 897 p.setYear() 898 p.stateTime = timeStart 899 break iterRunes 900 } 901 } 902 903 default: 904 break iterRunes 905 } 906 } 907 p.coalesceDate(i) 908 if p.stateTime == timeStart { 909 // increment first one, since the i++ occurs at end of loop 910 if i < len(p.datestr) { 911 i++ 912 } 913 // ensure we skip any whitespace prefix 914 for ; i < len(datestr); i++ { 915 r := rune(datestr[i]) 916 if r != ' ' { 917 break 918 } 919 } 920 921 iterTimeRunes: 922 for ; i < len(datestr); i++ { 923 r := rune(datestr[i]) 924 925 //gou.Debugf("%d %s %d iterTimeRunes %s %s", i, string(r), p.stateTime, p.ds(), p.ts()) 926 927 switch p.stateTime { 928 case timeStart: 929 // 22:43:22 930 // 22:43 931 // timeComma 932 // 08:20:13,787 933 // timeWs 934 // 05:24:37 PM 935 // 06:20:00 UTC 936 // 06:20:00 UTC-05 937 // 00:12:00 +0000 UTC 938 // 22:18:00 +0000 UTC m=+0.000000001 939 // 15:04:05 -0700 940 // 15:04:05 -07:00 941 // 15:04:05 2008 942 // timeOffset 943 // 03:21:51+00:00 944 // 19:55:00+0100 945 // timePeriod 946 // 17:24:37.3186369 947 // 00:07:31.945167 948 // 18:31:59.257000000 949 // 00:00:00.000 950 // timePeriodOffset 951 // 19:55:00.799+0100 952 // timePeriodOffsetColon 953 // 15:04:05.999-07:00 954 // timePeriodWs 955 // timePeriodWsOffset 956 // 00:07:31.945167 +0000 957 // 00:00:00.000 +0000 958 // timePeriodWsOffsetAlpha 959 // 00:07:31.945167 +0000 UTC 960 // 22:18:00.001 +0000 UTC m=+0.000000001 961 // 00:00:00.000 +0000 UTC 962 // timePeriodWsAlpha 963 // 06:20:00.000 UTC 964 if p.houri == 0 { 965 p.houri = i 966 } 967 switch r { 968 case ',': 969 // hm, lets just swap out comma for period. for some reason go 970 // won't parse it. 971 // 2014-05-11 08:20:13,787 972 ds := []byte(p.datestr) 973 ds[i] = '.' 974 return parseTime(string(ds), loc) 975 case '-', '+': 976 // 03:21:51+00:00 977 p.stateTime = timeOffset 978 if p.seci == 0 { 979 // 22:18+0530 980 p.minlen = i - p.mini 981 } else { 982 p.seclen = i - p.seci 983 } 984 p.offseti = i 985 case '.': 986 p.stateTime = timePeriod 987 p.seclen = i - p.seci 988 p.msi = i + 1 989 case 'Z': 990 p.stateTime = timeZ 991 if p.seci == 0 { 992 p.minlen = i - p.mini 993 } else { 994 p.seclen = i - p.seci 995 } 996 case 'a', 'A': 997 if p.nextIs(i, 't') || p.nextIs(i, 'T') { 998 // x 999 // September 17, 2012 at 5:00pm UTC-05 1000 i++ // skip t 1001 if p.nextIs(i, ' ') { 1002 // x 1003 // September 17, 2012 at 5:00pm UTC-05 1004 i++ // skip ' 1005 p.houri = 0 // reset hour 1006 } 1007 } else { 1008 switch { 1009 case r == 'a' && p.nextIs(i, 'm'): 1010 p.coalesceTime(i) 1011 p.set(i, "am") 1012 case r == 'A' && p.nextIs(i, 'M'): 1013 p.coalesceTime(i) 1014 p.set(i, "PM") 1015 } 1016 } 1017 1018 case 'p', 'P': 1019 // Could be AM/PM 1020 switch { 1021 case r == 'p' && p.nextIs(i, 'm'): 1022 p.coalesceTime(i) 1023 p.set(i, "pm") 1024 case r == 'P' && p.nextIs(i, 'M'): 1025 p.coalesceTime(i) 1026 p.set(i, "PM") 1027 } 1028 case ' ': 1029 p.coalesceTime(i) 1030 p.stateTime = timeWs 1031 case ':': 1032 if p.mini == 0 { 1033 p.mini = i + 1 1034 p.hourlen = i - p.houri 1035 } else if p.seci == 0 { 1036 p.seci = i + 1 1037 p.minlen = i - p.mini 1038 } 1039 } 1040 case timeOffset: 1041 // 19:55:00+0100 1042 // timeOffsetColon 1043 // 15:04:05+07:00 1044 // 15:04:05-07:00 1045 if r == ':' { 1046 p.stateTime = timeOffsetColon 1047 } 1048 case timeWs: 1049 // timeWsAlpha 1050 // 06:20:00 UTC 1051 // 06:20:00 UTC-05 1052 // 15:44:11 UTC+0100 2015 1053 // 18:04:07 GMT+0100 (GMT Daylight Time) 1054 // 17:57:51 MST 2009 1055 // timeWsAMPMMaybe 1056 // 05:24:37 PM 1057 // timeWsOffset 1058 // 15:04:05 -0700 1059 // 00:12:00 +0000 UTC 1060 // timeWsOffsetColon 1061 // 15:04:05 -07:00 1062 // 17:57:51 -0700 2009 1063 // timeWsOffsetColonAlpha 1064 // 00:12:00 +00:00 UTC 1065 // timeWsYear 1066 // 00:12:00 2008 1067 // timeZ 1068 // 15:04:05.99Z 1069 switch r { 1070 case 'A', 'P': 1071 // Could be AM/PM or could be PST or similar 1072 p.tzi = i 1073 p.stateTime = timeWsAMPMMaybe 1074 case '+', '-': 1075 p.offseti = i 1076 p.stateTime = timeWsOffset 1077 default: 1078 if unicode.IsLetter(r) { 1079 // 06:20:00 UTC 1080 // 06:20:00 UTC-05 1081 // 15:44:11 UTC+0100 2015 1082 // 17:57:51 MST 2009 1083 p.tzi = i 1084 p.stateTime = timeWsAlpha 1085 //break iterTimeRunes 1086 } else if unicode.IsDigit(r) { 1087 // 00:12:00 2008 1088 p.stateTime = timeWsYear 1089 p.yeari = i 1090 } 1091 } 1092 case timeWsAlpha: 1093 // 06:20:00 UTC 1094 // 06:20:00 UTC-05 1095 // timeWsAlphaWs 1096 // 17:57:51 MST 2009 1097 // timeWsAlphaZoneOffset 1098 // timeWsAlphaZoneOffsetWs 1099 // timeWsAlphaZoneOffsetWsExtra 1100 // 18:04:07 GMT+0100 (GMT Daylight Time) 1101 // timeWsAlphaZoneOffsetWsYear 1102 // 15:44:11 UTC+0100 2015 1103 switch r { 1104 case '+', '-': 1105 p.tzlen = i - p.tzi 1106 if p.tzlen == 4 { 1107 p.set(p.tzi, " MST") 1108 } else if p.tzlen == 3 { 1109 p.set(p.tzi, "MST") 1110 } 1111 p.stateTime = timeWsAlphaZoneOffset 1112 p.offseti = i 1113 case ' ': 1114 // 17:57:51 MST 2009 1115 p.tzlen = i - p.tzi 1116 if p.tzlen == 4 { 1117 p.set(p.tzi, " MST") 1118 } else if p.tzlen == 3 { 1119 p.set(p.tzi, "MST") 1120 } 1121 p.stateTime = timeWsAlphaWs 1122 p.yeari = i + 1 1123 } 1124 case timeWsAlphaWs: 1125 // 17:57:51 MST 2009 1126 1127 case timeWsAlphaZoneOffset: 1128 // 06:20:00 UTC-05 1129 // timeWsAlphaZoneOffset 1130 // timeWsAlphaZoneOffsetWs 1131 // timeWsAlphaZoneOffsetWsExtra 1132 // 18:04:07 GMT+0100 (GMT Daylight Time) 1133 // timeWsAlphaZoneOffsetWsYear 1134 // 15:44:11 UTC+0100 2015 1135 switch r { 1136 case ' ': 1137 p.set(p.offseti, "-0700") 1138 p.yeari = i + 1 1139 p.stateTime = timeWsAlphaZoneOffsetWs 1140 } 1141 case timeWsAlphaZoneOffsetWs: 1142 // timeWsAlphaZoneOffsetWs 1143 // timeWsAlphaZoneOffsetWsExtra 1144 // 18:04:07 GMT+0100 (GMT Daylight Time) 1145 // timeWsAlphaZoneOffsetWsYear 1146 // 15:44:11 UTC+0100 2015 1147 if unicode.IsDigit(r) { 1148 p.stateTime = timeWsAlphaZoneOffsetWsYear 1149 } else { 1150 p.extra = i - 1 1151 p.stateTime = timeWsAlphaZoneOffsetWsExtra 1152 } 1153 case timeWsAlphaZoneOffsetWsYear: 1154 // 15:44:11 UTC+0100 2015 1155 if unicode.IsDigit(r) { 1156 p.yearlen = i - p.yeari + 1 1157 if p.yearlen == 4 { 1158 p.setYear() 1159 } 1160 } 1161 case timeWsAMPMMaybe: 1162 // timeWsAMPMMaybe 1163 // timeWsAMPM 1164 // 05:24:37 PM 1165 // timeWsAlpha 1166 // 00:12:00 PST 1167 // 15:44:11 UTC+0100 2015 1168 if r == 'M' { 1169 //return parse("2006-01-02 03:04:05 PM", datestr, loc) 1170 p.stateTime = timeWsAMPM 1171 p.set(i-1, "PM") 1172 if p.hourlen == 2 { 1173 p.set(p.houri, "03") 1174 } else if p.hourlen == 1 { 1175 p.set(p.houri, "3") 1176 } 1177 } else { 1178 p.stateTime = timeWsAlpha 1179 } 1180 1181 case timeWsOffset: 1182 // timeWsOffset 1183 // 15:04:05 -0700 1184 // timeWsOffsetWsOffset 1185 // 17:57:51 -0700 -07 1186 // timeWsOffsetWs 1187 // 17:57:51 -0700 2009 1188 // 00:12:00 +0000 UTC 1189 // timeWsOffsetColon 1190 // 15:04:05 -07:00 1191 // timeWsOffsetColonAlpha 1192 // 00:12:00 +00:00 UTC 1193 switch r { 1194 case ':': 1195 p.stateTime = timeWsOffsetColon 1196 case ' ': 1197 p.set(p.offseti, "-0700") 1198 p.yeari = i + 1 1199 p.stateTime = timeWsOffsetWs 1200 } 1201 case timeWsOffsetWs: 1202 // 17:57:51 -0700 2009 1203 // 00:12:00 +0000 UTC 1204 // 22:18:00.001 +0000 UTC m=+0.000000001 1205 // w Extra 1206 // 17:57:51 -0700 -07 1207 switch r { 1208 case '=': 1209 // eff you golang 1210 if datestr[i-1] == 'm' { 1211 p.extra = i - 2 1212 p.trimExtra() 1213 break 1214 } 1215 case '+', '-': 1216 // This really doesn't seem valid, but for some reason when round-tripping a go date 1217 // their is an extra +03 printed out. seems like go bug to me, but, parsing anyway. 1218 // 00:00:00 +0300 +03 1219 // 00:00:00 +0300 +0300 1220 p.extra = i - 1 1221 p.stateTime = timeWsOffset 1222 p.trimExtra() 1223 break 1224 default: 1225 switch { 1226 case unicode.IsDigit(r): 1227 p.yearlen = i - p.yeari + 1 1228 if p.yearlen == 4 { 1229 p.setYear() 1230 } 1231 case unicode.IsLetter(r): 1232 if p.tzi == 0 { 1233 p.tzi = i 1234 } 1235 } 1236 } 1237 1238 case timeWsOffsetColon: 1239 // timeWsOffsetColon 1240 // 15:04:05 -07:00 1241 // timeWsOffsetColonAlpha 1242 // 2015-02-18 00:12:00 +00:00 UTC 1243 if unicode.IsLetter(r) { 1244 // 2015-02-18 00:12:00 +00:00 UTC 1245 p.stateTime = timeWsOffsetColonAlpha 1246 break iterTimeRunes 1247 } 1248 case timePeriod: 1249 // 15:04:05.999999999+07:00 1250 // 15:04:05.999999999-07:00 1251 // 15:04:05.999999+07:00 1252 // 15:04:05.999999-07:00 1253 // 15:04:05.999+07:00 1254 // 15:04:05.999-07:00 1255 // timePeriod 1256 // 17:24:37.3186369 1257 // 00:07:31.945167 1258 // 18:31:59.257000000 1259 // 00:00:00.000 1260 // timePeriodOffset 1261 // 19:55:00.799+0100 1262 // timePeriodOffsetColon 1263 // 15:04:05.999-07:00 1264 // timePeriodWs 1265 // timePeriodWsOffset 1266 // 00:07:31.945167 +0000 1267 // 00:00:00.000 +0000 1268 // With Extra 1269 // 00:00:00.000 +0300 +03 1270 // timePeriodWsOffsetAlpha 1271 // 00:07:31.945167 +0000 UTC 1272 // 00:00:00.000 +0000 UTC 1273 // 22:18:00.001 +0000 UTC m=+0.000000001 1274 // timePeriodWsAlpha 1275 // 06:20:00.000 UTC 1276 switch r { 1277 case ' ': 1278 p.mslen = i - p.msi 1279 p.stateTime = timePeriodWs 1280 case '+', '-': 1281 // This really shouldn't happen 1282 p.mslen = i - p.msi 1283 p.offseti = i 1284 p.stateTime = timePeriodOffset 1285 default: 1286 if unicode.IsLetter(r) { 1287 // 06:20:00.000 UTC 1288 p.mslen = i - p.msi 1289 p.stateTime = timePeriodWsAlpha 1290 } 1291 } 1292 case timePeriodOffset: 1293 // timePeriodOffset 1294 // 19:55:00.799+0100 1295 // timePeriodOffsetColon 1296 // 15:04:05.999-07:00 1297 // 13:31:51.999-07:00 MST 1298 if r == ':' { 1299 p.stateTime = timePeriodOffsetColon 1300 } 1301 case timePeriodOffsetColon: 1302 // timePeriodOffset 1303 // timePeriodOffsetColon 1304 // 15:04:05.999-07:00 1305 // 13:31:51.999 -07:00 MST 1306 switch r { 1307 case ' ': 1308 p.set(p.offseti, "-07:00") 1309 p.stateTime = timePeriodOffsetColonWs 1310 p.tzi = i + 1 1311 } 1312 case timePeriodOffsetColonWs: 1313 // continue 1314 case timePeriodWs: 1315 // timePeriodWs 1316 // timePeriodWsOffset 1317 // 00:07:31.945167 +0000 1318 // 00:00:00.000 +0000 1319 // timePeriodWsOffsetAlpha 1320 // 00:07:31.945167 +0000 UTC 1321 // 00:00:00.000 +0000 UTC 1322 // timePeriodWsOffsetColon 1323 // 13:31:51.999 -07:00 MST 1324 // timePeriodWsAlpha 1325 // 06:20:00.000 UTC 1326 if p.offseti == 0 { 1327 p.offseti = i 1328 } 1329 switch r { 1330 case '+', '-': 1331 p.mslen = i - p.msi - 1 1332 p.stateTime = timePeriodWsOffset 1333 default: 1334 if unicode.IsLetter(r) { 1335 // 00:07:31.945167 +0000 UTC 1336 // 00:00:00.000 +0000 UTC 1337 p.stateTime = timePeriodWsOffsetWsAlpha 1338 break iterTimeRunes 1339 } 1340 } 1341 1342 case timePeriodWsOffset: 1343 // timePeriodWs 1344 // timePeriodWsOffset 1345 // 00:07:31.945167 +0000 1346 // 00:00:00.000 +0000 1347 // With Extra 1348 // 00:00:00.000 +0300 +03 1349 // timePeriodWsOffsetAlpha 1350 // 00:07:31.945167 +0000 UTC 1351 // 00:00:00.000 +0000 UTC 1352 // 03:02:00.001 +0300 MSK m=+0.000000001 1353 // timePeriodWsOffsetColon 1354 // 13:31:51.999 -07:00 MST 1355 // timePeriodWsAlpha 1356 // 06:20:00.000 UTC 1357 switch r { 1358 case ':': 1359 p.stateTime = timePeriodWsOffsetColon 1360 case ' ': 1361 p.set(p.offseti, "-0700") 1362 case '+', '-': 1363 // This really doesn't seem valid, but for some reason when round-tripping a go date 1364 // their is an extra +03 printed out. seems like go bug to me, but, parsing anyway. 1365 // 00:00:00.000 +0300 +03 1366 // 00:00:00.000 +0300 +0300 1367 p.extra = i - 1 1368 p.trimExtra() 1369 break 1370 default: 1371 if unicode.IsLetter(r) { 1372 // 00:07:31.945167 +0000 UTC 1373 // 00:00:00.000 +0000 UTC 1374 // 03:02:00.001 +0300 MSK m=+0.000000001 1375 p.stateTime = timePeriodWsOffsetWsAlpha 1376 } 1377 } 1378 case timePeriodWsOffsetWsAlpha: 1379 // 03:02:00.001 +0300 MSK m=+0.000000001 1380 // eff you golang 1381 if r == '=' && datestr[i-1] == 'm' { 1382 p.extra = i - 2 1383 p.trimExtra() 1384 break 1385 } 1386 1387 case timePeriodWsOffsetColon: 1388 // 13:31:51.999 -07:00 MST 1389 switch r { 1390 case ' ': 1391 p.set(p.offseti, "-07:00") 1392 default: 1393 if unicode.IsLetter(r) { 1394 // 13:31:51.999 -07:00 MST 1395 p.tzi = i 1396 p.stateTime = timePeriodWsOffsetColonAlpha 1397 } 1398 } 1399 case timePeriodWsOffsetColonAlpha: 1400 // continue 1401 case timeZ: 1402 // timeZ 1403 // 15:04:05.99Z 1404 // With a time-zone at end after Z 1405 // 2006-01-02T15:04:05.999999999Z07:00 1406 // 2006-01-02T15:04:05Z07:00 1407 // RFC3339 = "2006-01-02T15:04:05Z07:00" 1408 // RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" 1409 if unicode.IsDigit(r) { 1410 p.stateTime = timeZDigit 1411 } 1412 1413 } 1414 } 1415 1416 switch p.stateTime { 1417 case timeWsAlphaWs: 1418 p.yearlen = i - p.yeari 1419 p.setYear() 1420 case timeWsYear: 1421 p.yearlen = i - p.yeari 1422 p.setYear() 1423 case timeWsAlphaZoneOffsetWsExtra: 1424 p.trimExtra() 1425 case timeWsAlphaZoneOffset: 1426 // 06:20:00 UTC-05 1427 if i-p.offseti < 4 { 1428 p.set(p.offseti, "-07") 1429 } else { 1430 p.set(p.offseti, "-0700") 1431 } 1432 1433 case timePeriod: 1434 p.mslen = i - p.msi 1435 case timeOffset: 1436 // 19:55:00+0100 1437 p.set(p.offseti, "-0700") 1438 case timeWsOffset: 1439 p.set(p.offseti, "-0700") 1440 case timeWsOffsetWs: 1441 // 17:57:51 -0700 2009 1442 // 00:12:00 +0000 UTC 1443 case timeWsOffsetColon: 1444 // 17:57:51 -07:00 1445 p.set(p.offseti, "-07:00") 1446 case timeOffsetColon: 1447 // 15:04:05+07:00 1448 p.set(p.offseti, "-07:00") 1449 case timePeriodOffset: 1450 // 19:55:00.799+0100 1451 p.set(p.offseti, "-0700") 1452 case timePeriodOffsetColon: 1453 p.set(p.offseti, "-07:00") 1454 case timePeriodWsOffsetColonAlpha: 1455 p.tzlen = i - p.tzi 1456 switch p.tzlen { 1457 case 3: 1458 p.set(p.tzi, "MST") 1459 case 4: 1460 p.set(p.tzi, "MST ") 1461 } 1462 case timePeriodWsOffset: 1463 p.set(p.offseti, "-0700") 1464 } 1465 p.coalesceTime(i) 1466 } 1467 1468 switch p.stateDate { 1469 case dateDigit: 1470 // unixy timestamps ish 1471 // example ct type 1472 // 1499979655583057426 19 nanoseconds 1473 // 1499979795437000 16 micro-seconds 1474 // 20180722105203 14 yyyyMMddhhmmss 1475 // 1499979795437 13 milliseconds 1476 // 1332151919 10 seconds 1477 // 20140601 8 yyyymmdd 1478 // 2014 4 yyyy 1479 t := time.Time{} 1480 if len(datestr) == len("1499979655583057426") { // 19 1481 // nano-seconds 1482 if nanoSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 1483 t = time.Unix(0, nanoSecs) 1484 } 1485 } else if len(datestr) == len("1499979795437000") { // 16 1486 // micro-seconds 1487 if microSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 1488 t = time.Unix(0, microSecs*1000) 1489 } 1490 } else if len(datestr) == len("yyyyMMddhhmmss") { // 14 1491 // yyyyMMddhhmmss 1492 p.format = []byte("20060102150405") 1493 return p, nil 1494 } else if len(datestr) == len("1332151919000") { // 13 1495 if miliSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 1496 t = time.Unix(0, miliSecs*1000*1000) 1497 } 1498 } else if len(datestr) == len("1332151919") { //10 1499 if secs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 1500 t = time.Unix(secs, 0) 1501 } 1502 } else if len(datestr) == len("20140601") { 1503 p.format = []byte("20060102") 1504 return p, nil 1505 } else if len(datestr) == len("2014") { 1506 p.format = []byte("2006") 1507 return p, nil 1508 } else if len(datestr) < 4 { 1509 return nil, fmt.Errorf("unrecognized format, too short %v", datestr) 1510 } 1511 if !t.IsZero() { 1512 if loc == nil { 1513 p.t = &t 1514 return p, nil 1515 } 1516 t = t.In(loc) 1517 p.t = &t 1518 return p, nil 1519 } 1520 1521 case dateYearDash: 1522 // 2006-01 1523 return p, nil 1524 1525 case dateYearDashDash: 1526 // 2006-01-02 1527 // 2006-1-02 1528 // 2006-1-2 1529 // 2006-01-2 1530 return p, nil 1531 1532 case dateYearDashAlphaDash: 1533 // 2013-Feb-03 1534 // 2013-Feb-3 1535 p.daylen = i - p.dayi 1536 p.setDay() 1537 return p, nil 1538 1539 case dateYearDashDashWs: 1540 // 2013-04-01 1541 return p, nil 1542 1543 case dateYearDashDashT: 1544 return p, nil 1545 1546 case dateDigitDashAlphaDash: 1547 // 13-Feb-03 ambiguous 1548 // 28-Feb-03 ambiguous 1549 // 29-Jun-2016 1550 length := len(datestr) - (p.moi + p.molen + 1) 1551 if length == 4 { 1552 p.yearlen = 4 1553 p.set(p.yeari, "2006") 1554 // We now also know that part1 was the day 1555 p.dayi = 0 1556 p.daylen = p.part1Len 1557 p.setDay() 1558 } else if length == 2 { 1559 // We have no idea if this is 1560 // yy-mon-dd OR dd-mon-yy 1561 // 1562 // We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption 1563 p.ambiguousMD = true 1564 p.yearlen = 2 1565 p.set(p.yeari, "06") 1566 // We now also know that part1 was the day 1567 p.dayi = 0 1568 p.daylen = p.part1Len 1569 p.setDay() 1570 } 1571 1572 return p, nil 1573 1574 case dateDigitDot: 1575 // 2014.05 1576 p.molen = i - p.moi 1577 p.setMonth() 1578 return p, nil 1579 1580 case dateDigitDotDot: 1581 // 03.31.1981 1582 // 3.31.2014 1583 // 3.2.1981 1584 // 3.2.81 1585 // 08.21.71 1586 // 2018.09.30 1587 return p, nil 1588 1589 case dateDigitWsMoYear: 1590 // 2 Jan 2018 1591 // 2 Jan 18 1592 // 2 Jan 2018 23:59 1593 // 02 Jan 2018 23:59 1594 // 12 Feb 2006, 19:17 1595 return p, nil 1596 1597 case dateDigitWsMolong: 1598 // 18 January 2018 1599 // 8 January 2018 1600 if p.daylen == 2 { 1601 p.format = []byte("02 January 2006") 1602 return p, nil 1603 } 1604 p.format = []byte("2 January 2006") 1605 return p, nil // parse("2 January 2006", datestr, loc) 1606 1607 case dateAlphaWsMonth: 1608 p.yearlen = i - p.yeari 1609 p.setYear() 1610 return p, nil 1611 1612 case dateAlphaWsMonthMore: 1613 return p, nil 1614 1615 case dateAlphaWsDigitMoreWs: 1616 // oct 1, 1970 1617 p.yearlen = i - p.yeari 1618 p.setYear() 1619 return p, nil 1620 1621 case dateAlphaWsDigitMoreWsYear: 1622 // May 8, 2009 5:57:51 PM 1623 // Jun 7, 2005, 05:57:51 1624 return p, nil 1625 1626 case dateAlphaWsAlpha: 1627 return p, nil 1628 1629 case dateAlphaWsAlphaYearmaybe: 1630 return p, nil 1631 1632 case dateDigitSlash: 1633 // 3/1/2014 1634 // 10/13/2014 1635 // 01/02/2006 1636 // 2014/10/13 1637 return p, nil 1638 1639 case dateDigitChineseYear: 1640 // dateDigitChineseYear 1641 // 2014年04月08日 1642 p.format = []byte("2006年01月02日") 1643 return p, nil 1644 1645 case dateDigitChineseYearWs: 1646 p.format = []byte("2006年01月02日 15:04:05") 1647 return p, nil 1648 1649 case dateWeekdayComma: 1650 // Monday, 02 Jan 2006 15:04:05 -0700 1651 // Monday, 02 Jan 2006 15:04:05 +0100 1652 // Monday, 02-Jan-06 15:04:05 MST 1653 return p, nil 1654 1655 case dateWeekdayAbbrevComma: 1656 // Mon, 02-Jan-06 15:04:05 MST 1657 // Mon, 02 Jan 2006 15:04:05 MST 1658 return p, nil 1659 1660 } 1661 1662 return nil, unknownErr(datestr) 1663 } 1664 1665 type parser struct { 1666 loc *time.Location 1667 preferMonthFirst bool 1668 ambiguousMD bool 1669 stateDate dateState 1670 stateTime timeState 1671 format []byte 1672 datestr string 1673 fullMonth string 1674 skip int 1675 extra int 1676 part1Len int 1677 yeari int 1678 yearlen int 1679 moi int 1680 molen int 1681 dayi int 1682 daylen int 1683 houri int 1684 hourlen int 1685 mini int 1686 minlen int 1687 seci int 1688 seclen int 1689 msi int 1690 mslen int 1691 offseti int 1692 offsetlen int 1693 tzi int 1694 tzlen int 1695 t *time.Time 1696 } 1697 1698 func newParser(dateStr string, loc *time.Location) *parser { 1699 p := parser{ 1700 stateDate: dateStart, 1701 stateTime: timeIgnore, 1702 datestr: dateStr, 1703 loc: loc, 1704 preferMonthFirst: true, 1705 } 1706 p.format = []byte(dateStr) 1707 return &p 1708 } 1709 1710 func (p *parser) nextIs(i int, b byte) bool { 1711 if len(p.datestr) > i+1 && p.datestr[i+1] == b { 1712 return true 1713 } 1714 return false 1715 } 1716 1717 func (p *parser) set(start int, val string) { 1718 if start < 0 { 1719 return 1720 } 1721 if len(p.format) < start+len(val) { 1722 return 1723 } 1724 for i, r := range val { 1725 p.format[start+i] = byte(r) 1726 } 1727 } 1728 func (p *parser) setMonth() { 1729 if p.molen == 2 { 1730 p.set(p.moi, "01") 1731 } else if p.molen == 1 { 1732 p.set(p.moi, "1") 1733 } 1734 } 1735 1736 func (p *parser) setDay() { 1737 if p.daylen == 2 { 1738 p.set(p.dayi, "02") 1739 } else if p.daylen == 1 { 1740 p.set(p.dayi, "2") 1741 } 1742 } 1743 func (p *parser) setYear() { 1744 if p.yearlen == 2 { 1745 p.set(p.yeari, "06") 1746 } else if p.yearlen == 4 { 1747 p.set(p.yeari, "2006") 1748 } 1749 } 1750 func (p *parser) coalesceDate(end int) { 1751 if p.yeari > 0 { 1752 if p.yearlen == 0 { 1753 p.yearlen = end - p.yeari 1754 } 1755 p.setYear() 1756 } 1757 if p.moi > 0 && p.molen == 0 { 1758 p.molen = end - p.moi 1759 p.setMonth() 1760 } 1761 if p.dayi > 0 && p.daylen == 0 { 1762 p.daylen = end - p.dayi 1763 p.setDay() 1764 } 1765 } 1766 func (p *parser) ts() string { 1767 return fmt.Sprintf("h:(%d:%d) m:(%d:%d) s:(%d:%d)", p.houri, p.hourlen, p.mini, p.minlen, p.seci, p.seclen) 1768 } 1769 func (p *parser) ds() string { 1770 return fmt.Sprintf("%s d:(%d:%d) m:(%d:%d) y:(%d:%d)", p.datestr, p.dayi, p.daylen, p.moi, p.molen, p.yeari, p.yearlen) 1771 } 1772 func (p *parser) coalesceTime(end int) { 1773 // 03:04:05 1774 // 15:04:05 1775 // 3:04:05 1776 // 3:4:5 1777 // 15:04:05.00 1778 if p.houri > 0 { 1779 if p.hourlen == 2 { 1780 p.set(p.houri, "15") 1781 } else if p.hourlen == 1 { 1782 p.set(p.houri, "3") 1783 } 1784 } 1785 if p.mini > 0 { 1786 if p.minlen == 0 { 1787 p.minlen = end - p.mini 1788 } 1789 if p.minlen == 2 { 1790 p.set(p.mini, "04") 1791 } else { 1792 p.set(p.mini, "4") 1793 } 1794 } 1795 if p.seci > 0 { 1796 if p.seclen == 0 { 1797 p.seclen = end - p.seci 1798 } 1799 if p.seclen == 2 { 1800 p.set(p.seci, "05") 1801 } else { 1802 p.set(p.seci, "5") 1803 } 1804 } 1805 1806 if p.msi > 0 { 1807 for i := 0; i < p.mslen; i++ { 1808 p.format[p.msi+i] = '0' 1809 } 1810 } 1811 } 1812 func (p *parser) setFullMonth(month string) { 1813 if p.moi == 0 { 1814 p.format = []byte(fmt.Sprintf("%s%s", "January", p.format[len(month):])) 1815 } 1816 } 1817 1818 func (p *parser) trimExtra() { 1819 if p.extra > 0 && len(p.format) > p.extra { 1820 p.format = p.format[0:p.extra] 1821 p.datestr = p.datestr[0:p.extra] 1822 } 1823 } 1824 1825 // func (p *parser) remove(i, length int) { 1826 // if len(p.format) > i+length { 1827 // //append(a[:i], a[j:]...) 1828 // p.format = append(p.format[0:i], p.format[i+length:]...) 1829 // } 1830 // if len(p.datestr) > i+length { 1831 // //append(a[:i], a[j:]...) 1832 // p.datestr = fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+length:]) 1833 // } 1834 // } 1835 1836 func (p *parser) parse() (time.Time, error) { 1837 if p.t != nil { 1838 return *p.t, nil 1839 } 1840 if len(p.fullMonth) > 0 { 1841 p.setFullMonth(p.fullMonth) 1842 } 1843 if p.skip > 0 && len(p.format) > p.skip { 1844 p.format = p.format[p.skip:] 1845 p.datestr = p.datestr[p.skip:] 1846 } 1847 //gou.Debugf("parse %q AS %q", p.datestr, string(p.format)) 1848 if p.loc == nil { 1849 return time.Parse(string(p.format), p.datestr) 1850 } 1851 return time.ParseInLocation(string(p.format), p.datestr, p.loc) 1852 } 1853 func isMonthFull(alpha string) bool { 1854 for _, month := range months { 1855 if alpha == month { 1856 return true 1857 } 1858 } 1859 return false 1860 }