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  }