parseany.go

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