github.com/chain5j/chain5j-pkg@v1.0.7/util/dateutil/date2.go (about)

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