github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/ztime/cron/cronexpr.go (about)

     1  package cron
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"sort"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  )
    11  
    12  // copyright https://github.com/gorhill/cronexpr
    13  
    14  type (
    15  	Expression struct {
    16  		daysOfWeek             map[int]bool
    17  		lastWeekDaysOfWeek     map[int]bool
    18  		specificWeekDaysOfWeek map[int]bool
    19  		daysOfMonth            map[int]bool
    20  		workdaysOfMonth        map[int]bool
    21  		monthList              []int
    22  		actualDaysOfMonthList  []int
    23  		secondList             []int
    24  		hourList               []int
    25  		minuteList             []int
    26  		yearList               []int
    27  		lastWorkdayOfMonth     bool
    28  		daysOfMonthRestricted  bool
    29  		lastDayOfMonth         bool
    30  		daysOfWeekRestricted   bool
    31  	}
    32  	cronDirective struct {
    33  		kind  int
    34  		first int
    35  		last  int
    36  		step  int
    37  		sbeg  int
    38  		send  int
    39  	}
    40  )
    41  
    42  const (
    43  	none = 0
    44  	one  = 1
    45  	span = 2
    46  	all  = 3
    47  )
    48  
    49  var (
    50  	dowNormalizedOffsets = [][]int{
    51  		{1, 8, 15, 22, 29},
    52  		{2, 9, 16, 23, 30},
    53  		{3, 10, 17, 24, 31},
    54  		{4, 11, 18, 25},
    55  		{5, 12, 19, 26},
    56  		{6, 13, 20, 27},
    57  		{7, 14, 21, 28},
    58  	}
    59  	genericDefaultList = []int{
    60  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    61  		10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    62  		20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
    63  		30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    64  		40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
    65  		50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
    66  	}
    67  	yearDefaultList = []int{
    68  		1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979,
    69  		1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989,
    70  		1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
    71  		2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
    72  		2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
    73  		2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029,
    74  		2030, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039,
    75  		2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047, 2048, 2049,
    76  		2050, 2051, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059,
    77  		2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069,
    78  		2070, 2071, 2072, 2073, 2074, 2075, 2076, 2077, 2078, 2079,
    79  		2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089,
    80  		2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099,
    81  	}
    82  	numberTokens = map[string]int{
    83  		"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9,
    84  		"00": 0, "01": 1, "02": 2, "03": 3, "04": 4, "05": 5, "06": 6, "07": 7, "08": 8, "09": 9,
    85  		"10": 10, "11": 11, "12": 12, "13": 13, "14": 14, "15": 15, "16": 16, "17": 17, "18": 18, "19": 19,
    86  		"20": 20, "21": 21, "22": 22, "23": 23, "24": 24, "25": 25, "26": 26, "27": 27, "28": 28, "29": 29,
    87  		"30": 30, "31": 31, "32": 32, "33": 33, "34": 34, "35": 35, "36": 36, "37": 37, "38": 38, "39": 39,
    88  		"40": 40, "41": 41, "42": 42, "43": 43, "44": 44, "45": 45, "46": 46, "47": 47, "48": 48, "49": 49,
    89  		"50": 50, "51": 51, "52": 52, "53": 53, "54": 54, "55": 55, "56": 56, "57": 57, "58": 58, "59": 59,
    90  		"1970": 1970, "1971": 1971, "1972": 1972, "1973": 1973, "1974": 1974, "1975": 1975, "1976": 1976, "1977": 1977, "1978": 1978, "1979": 1979,
    91  		"1980": 1980, "1981": 1981, "1982": 1982, "1983": 1983, "1984": 1984, "1985": 1985, "1986": 1986, "1987": 1987, "1988": 1988, "1989": 1989,
    92  		"1990": 1990, "1991": 1991, "1992": 1992, "1993": 1993, "1994": 1994, "1995": 1995, "1996": 1996, "1997": 1997, "1998": 1998, "1999": 1999,
    93  		"2000": 2000, "2001": 2001, "2002": 2002, "2003": 2003, "2004": 2004, "2005": 2005, "2006": 2006, "2007": 2007, "2008": 2008, "2009": 2009,
    94  		"2010": 2010, "2011": 2011, "2012": 2012, "2013": 2013, "2014": 2014, "2015": 2015, "2016": 2016, "2017": 2017, "2018": 2018, "2019": 2019,
    95  		"2020": 2020, "2021": 2021, "2022": 2022, "2023": 2023, "2024": 2024, "2025": 2025, "2026": 2026, "2027": 2027, "2028": 2028, "2029": 2029,
    96  		"2030": 2030, "2031": 2031, "2032": 2032, "2033": 2033, "2034": 2034, "2035": 2035, "2036": 2036, "2037": 2037, "2038": 2038, "2039": 2039,
    97  		"2040": 2040, "2041": 2041, "2042": 2042, "2043": 2043, "2044": 2044, "2045": 2045, "2046": 2046, "2047": 2047, "2048": 2048, "2049": 2049,
    98  		"2050": 2050, "2051": 2051, "2052": 2052, "2053": 2053, "2054": 2054, "2055": 2055, "2056": 2056, "2057": 2057, "2058": 2058, "2059": 2059,
    99  		"2060": 2060, "2061": 2061, "2062": 2062, "2063": 2063, "2064": 2064, "2065": 2065, "2066": 2066, "2067": 2067, "2068": 2068, "2069": 2069,
   100  		"2070": 2070, "2071": 2071, "2072": 2072, "2073": 2073, "2074": 2074, "2075": 2075, "2076": 2076, "2077": 2077, "2078": 2078, "2079": 2079,
   101  		"2080": 2080, "2081": 2081, "2082": 2082, "2083": 2083, "2084": 2084, "2085": 2085, "2086": 2086, "2087": 2087, "2088": 2088, "2089": 2089,
   102  		"2090": 2090, "2091": 2091, "2092": 2092, "2093": 2093, "2094": 2094, "2095": 2095, "2096": 2096, "2097": 2097, "2098": 2098, "2099": 2099,
   103  	}
   104  	monthTokens = map[string]int{
   105  		`1`: 1, `jan`: 1, `january`: 1,
   106  		`2`: 2, `feb`: 2, `february`: 2,
   107  		`3`: 3, `mar`: 3, `march`: 3,
   108  		`4`: 4, `apr`: 4, `april`: 4,
   109  		`5`: 5, `may`: 5,
   110  		`6`: 6, `jun`: 6, `june`: 6,
   111  		`7`: 7, `jul`: 7, `july`: 7,
   112  		`8`: 8, `aug`: 8, `august`: 8,
   113  		`9`: 9, `sep`: 9, `september`: 9,
   114  		`10`: 10, `oct`: 10, `october`: 10,
   115  		`11`: 11, `nov`: 11, `november`: 11,
   116  		`12`: 12, `dec`: 12, `december`: 12,
   117  	}
   118  	dowTokens = map[string]int{
   119  		`0`: 0, `sun`: 0, `sunday`: 0,
   120  		`1`: 1, `mon`: 1, `monday`: 1,
   121  		`2`: 2, `tue`: 2, `tuesday`: 2,
   122  		`3`: 3, `wed`: 3, `wednesday`: 3,
   123  		`4`: 4, `thu`: 4, `thursday`: 4,
   124  		`5`: 5, `fri`: 5, `friday`: 5,
   125  		`6`: 6, `sat`: 6, `saturday`: 6,
   126  		`7`: 0,
   127  	}
   128  )
   129  
   130  func atoi(s string) int {
   131  	return numberTokens[s]
   132  }
   133  
   134  type fieldDescriptor struct {
   135  	atoi         func(string) int
   136  	name         string
   137  	valuePattern string
   138  	defaultList  []int
   139  	min          int
   140  	max          int
   141  }
   142  
   143  var (
   144  	secondDescriptor = fieldDescriptor{
   145  		name:         "second",
   146  		min:          0,
   147  		max:          59,
   148  		defaultList:  genericDefaultList[0:60],
   149  		valuePattern: `0?[0-9]|[1-5][0-9]`,
   150  		atoi:         atoi,
   151  	}
   152  	minuteDescriptor = fieldDescriptor{
   153  		name:         "minute",
   154  		min:          0,
   155  		max:          59,
   156  		defaultList:  genericDefaultList[0:60],
   157  		valuePattern: `0?[0-9]|[1-5][0-9]`,
   158  		atoi:         atoi,
   159  	}
   160  	hourDescriptor = fieldDescriptor{
   161  		name:         "hour",
   162  		min:          0,
   163  		max:          23,
   164  		defaultList:  genericDefaultList[0:24],
   165  		valuePattern: `0?[0-9]|1[0-9]|2[0-3]`,
   166  		atoi:         atoi,
   167  	}
   168  	domDescriptor = fieldDescriptor{
   169  		name:         "day-of-month",
   170  		min:          1,
   171  		max:          31,
   172  		defaultList:  genericDefaultList[1:32],
   173  		valuePattern: `0?[1-9]|[12][0-9]|3[01]`,
   174  		atoi:         atoi,
   175  	}
   176  	monthDescriptor = fieldDescriptor{
   177  		name:         "month",
   178  		min:          1,
   179  		max:          12,
   180  		defaultList:  genericDefaultList[1:13],
   181  		valuePattern: `0?[1-9]|1[012]|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|march|april|june|july|august|september|october|november|december`,
   182  		atoi: func(s string) int {
   183  			return monthTokens[s]
   184  		},
   185  	}
   186  	dowDescriptor = fieldDescriptor{
   187  		name:         "day-of-week",
   188  		min:          0,
   189  		max:          6,
   190  		defaultList:  genericDefaultList[0:7],
   191  		valuePattern: `0?[0-7]|sun|mon|tue|wed|thu|fri|sat|sunday|monday|tuesday|wednesday|thursday|friday|saturday`,
   192  		atoi: func(s string) int {
   193  			return dowTokens[s]
   194  		},
   195  	}
   196  	yearDescriptor = fieldDescriptor{
   197  		name:         "year",
   198  		min:          1970,
   199  		max:          2099,
   200  		defaultList:  yearDefaultList[:],
   201  		valuePattern: `19[789][0-9]|20[0-9]{2}`,
   202  		atoi:         atoi,
   203  	}
   204  	layoutWildcard            = `^\*$|^\?$`
   205  	layoutValue               = `^(%value%)$`
   206  	layoutRange               = `^(%value%)-(%value%)$`
   207  	layoutWildcardAndInterval = `^\*/(\d+)$`
   208  	layoutValueAndInterval    = `^(%value%)/(\d+)$`
   209  	layoutRangeAndInterval    = `^(%value%)-(%value%)/(\d+)$`
   210  	layoutLastDom             = `^l$`
   211  	layoutWorkdom             = `^(%value%)w$`
   212  	layoutLastWorkdom         = `^lw$`
   213  	layoutDowOfLastWeek       = `^(%value%)l$`
   214  	layoutDowOfSpecificWeek   = `^(%value%)#([1-5])$`
   215  	fieldFinder               = regexp.MustCompile(`\S+`)
   216  	entryFinder               = regexp.MustCompile(`[^,]+`)
   217  	layoutRegexp              = make(map[string]*regexp.Regexp)
   218  	layoutRegexpLock          sync.Mutex
   219  	cronNormalizer            = strings.NewReplacer(
   220  		"@yearly", "0 0 0 1 1 * *",
   221  		"@annually", "0 0 0 1 1 * *",
   222  		"@monthly", "0 0 0 1 * * *",
   223  		"@weekly", "0 0 0 * * 0 *",
   224  		"@daily", "0 0 0 * * * *",
   225  		"@hourly", "0 0 * * * * *")
   226  )
   227  
   228  func (expr *Expression) secondFieldHandler(s string) error {
   229  	var err error
   230  	expr.secondList, err = genericFieldHandler(s, secondDescriptor)
   231  	return err
   232  }
   233  
   234  func (expr *Expression) minuteFieldHandler(s string) error {
   235  	var err error
   236  	expr.minuteList, err = genericFieldHandler(s, minuteDescriptor)
   237  	return err
   238  }
   239  func (expr *Expression) hourFieldHandler(s string) error {
   240  	var err error
   241  	expr.hourList, err = genericFieldHandler(s, hourDescriptor)
   242  	return err
   243  }
   244  
   245  func (expr *Expression) monthFieldHandler(s string) error {
   246  	var err error
   247  	expr.monthList, err = genericFieldHandler(s, monthDescriptor)
   248  	return err
   249  }
   250  func (expr *Expression) yearFieldHandler(s string) error {
   251  	var err error
   252  	expr.yearList, err = genericFieldHandler(s, yearDescriptor)
   253  	return err
   254  }
   255  
   256  func genericFieldHandler(s string, desc fieldDescriptor) ([]int, error) {
   257  	directives, err := genericFieldParse(s, desc)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	values := make(map[int]bool)
   262  	for _, directive := range directives {
   263  		switch directive.kind {
   264  		case none:
   265  			return nil, fmt.Errorf("syntax error in %s field: '%s'", desc.name, s[directive.sbeg:directive.send])
   266  		case one:
   267  			populateOne(values, directive.first)
   268  		case span:
   269  			populateMany(values, directive.first, directive.last, directive.step)
   270  		case all:
   271  			return desc.defaultList, nil
   272  		}
   273  	}
   274  	return toList(values), nil
   275  }
   276  
   277  func (expr *Expression) dowFieldHandler(s string) error {
   278  	expr.daysOfWeekRestricted = true
   279  	expr.daysOfWeek = make(map[int]bool)
   280  	expr.lastWeekDaysOfWeek = make(map[int]bool)
   281  	expr.specificWeekDaysOfWeek = make(map[int]bool)
   282  
   283  	directives, err := genericFieldParse(s, dowDescriptor)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	for _, directive := range directives {
   289  		switch directive.kind {
   290  		case none:
   291  			sdirective := s[directive.sbeg:directive.send]
   292  			snormal := strings.ToLower(sdirective)
   293  			// `5L`
   294  			pairs := makeLayoutRegexp(layoutDowOfLastWeek, dowDescriptor.valuePattern).FindStringSubmatchIndex(snormal)
   295  			if len(pairs) > 0 {
   296  				populateOne(expr.lastWeekDaysOfWeek, dowDescriptor.atoi(snormal[pairs[2]:pairs[3]]))
   297  			} else {
   298  				// `5#3`
   299  				pairs := makeLayoutRegexp(layoutDowOfSpecificWeek, dowDescriptor.valuePattern).FindStringSubmatchIndex(snormal)
   300  				if len(pairs) > 0 {
   301  					populateOne(expr.specificWeekDaysOfWeek, (dowDescriptor.atoi(snormal[pairs[4]:pairs[5]])-1)*7+(dowDescriptor.atoi(snormal[pairs[2]:pairs[3]])%7))
   302  				} else {
   303  					return fmt.Errorf("syntax error in day-of-week field: '%s'", sdirective)
   304  				}
   305  			}
   306  		case one:
   307  			populateOne(expr.daysOfWeek, directive.first)
   308  		case span:
   309  			populateMany(expr.daysOfWeek, directive.first, directive.last, directive.step)
   310  		case all:
   311  			populateMany(expr.daysOfWeek, directive.first, directive.last, directive.step)
   312  			expr.daysOfWeekRestricted = false
   313  		}
   314  	}
   315  	return nil
   316  }
   317  
   318  func (expr *Expression) domFieldHandler(s string) error {
   319  	expr.daysOfMonthRestricted = true
   320  	expr.lastDayOfMonth = false
   321  	expr.lastWorkdayOfMonth = false
   322  	expr.daysOfMonth = make(map[int]bool)     // days of month map
   323  	expr.workdaysOfMonth = make(map[int]bool) // work days of month map
   324  
   325  	directives, err := genericFieldParse(s, domDescriptor)
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	for _, directive := range directives {
   331  		switch directive.kind {
   332  		case none:
   333  			sdirective := s[directive.sbeg:directive.send]
   334  			snormal := strings.ToLower(sdirective)
   335  			// `L`
   336  			if makeLayoutRegexp(layoutLastDom, domDescriptor.valuePattern).MatchString(snormal) {
   337  				expr.lastDayOfMonth = true
   338  			} else {
   339  				// `LW`
   340  				if makeLayoutRegexp(layoutLastWorkdom, domDescriptor.valuePattern).MatchString(snormal) {
   341  					expr.lastWorkdayOfMonth = true
   342  				} else {
   343  					// `15W`
   344  					pairs := makeLayoutRegexp(layoutWorkdom, domDescriptor.valuePattern).FindStringSubmatchIndex(snormal)
   345  					if len(pairs) > 0 {
   346  						populateOne(expr.workdaysOfMonth, domDescriptor.atoi(snormal[pairs[2]:pairs[3]]))
   347  					} else {
   348  						return fmt.Errorf("syntax error in day-of-month field: '%s'", sdirective)
   349  					}
   350  				}
   351  			}
   352  		case one:
   353  			populateOne(expr.daysOfMonth, directive.first)
   354  		case span:
   355  			populateMany(expr.daysOfMonth, directive.first, directive.last, directive.step)
   356  		case all:
   357  			populateMany(expr.daysOfMonth, directive.first, directive.last, directive.step)
   358  			expr.daysOfMonthRestricted = false
   359  		}
   360  	}
   361  	return nil
   362  }
   363  
   364  /******************************************************************************/
   365  
   366  func populateOne(values map[int]bool, v int) {
   367  	values[v] = true
   368  }
   369  
   370  func populateMany(values map[int]bool, min, max, step int) {
   371  	for i := min; i <= max; i += step {
   372  		values[i] = true
   373  	}
   374  }
   375  
   376  func toList(set map[int]bool) []int {
   377  	list := make([]int, len(set))
   378  	i := 0
   379  	for k := range set {
   380  		list[i] = k
   381  		i += 1
   382  	}
   383  	sort.Ints(list)
   384  	return list
   385  }
   386  func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) {
   387  	// At least one entry must be present
   388  	indices := entryFinder.FindAllStringIndex(s, -1)
   389  	if len(indices) == 0 {
   390  		return nil, fmt.Errorf("%s field: missing directive", desc.name)
   391  	}
   392  
   393  	directives := make([]*cronDirective, 0, len(indices))
   394  
   395  	for i := range indices {
   396  		directive := cronDirective{
   397  			sbeg: indices[i][0],
   398  			send: indices[i][1],
   399  		}
   400  		snormal := strings.ToLower(s[indices[i][0]:indices[i][1]])
   401  
   402  		// `*`
   403  		if makeLayoutRegexp(layoutWildcard, desc.valuePattern).MatchString(snormal) {
   404  			directive.kind = all
   405  			directive.first = desc.min
   406  			directive.last = desc.max
   407  			directive.step = 1
   408  			directives = append(directives, &directive)
   409  			continue
   410  		}
   411  		// `5`
   412  		if makeLayoutRegexp(layoutValue, desc.valuePattern).MatchString(snormal) {
   413  			directive.kind = one
   414  			directive.first = desc.atoi(snormal)
   415  			directives = append(directives, &directive)
   416  			continue
   417  		}
   418  		// `5-20`
   419  		pairs := makeLayoutRegexp(layoutRange, desc.valuePattern).FindStringSubmatchIndex(snormal)
   420  		if len(pairs) > 0 {
   421  			directive.kind = span
   422  			directive.first = desc.atoi(snormal[pairs[2]:pairs[3]])
   423  			directive.last = desc.atoi(snormal[pairs[4]:pairs[5]])
   424  			directive.step = 1
   425  			directives = append(directives, &directive)
   426  			continue
   427  		}
   428  		// `*/2`
   429  		pairs = makeLayoutRegexp(layoutWildcardAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal)
   430  		if len(pairs) > 0 {
   431  			directive.kind = span
   432  			directive.first = desc.min
   433  			directive.last = desc.max
   434  			directive.step = atoi(snormal[pairs[2]:pairs[3]])
   435  			if directive.step < 1 || directive.step > desc.max {
   436  				return nil, fmt.Errorf("invalid interval %s", snormal)
   437  			}
   438  			directives = append(directives, &directive)
   439  			continue
   440  		}
   441  		// `5/2`
   442  		pairs = makeLayoutRegexp(layoutValueAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal)
   443  		if len(pairs) > 0 {
   444  			directive.kind = span
   445  			directive.first = desc.atoi(snormal[pairs[2]:pairs[3]])
   446  			directive.last = desc.max
   447  			directive.step = atoi(snormal[pairs[4]:pairs[5]])
   448  			if directive.step < 1 || directive.step > desc.max {
   449  				return nil, fmt.Errorf("invalid interval %s", snormal)
   450  			}
   451  			directives = append(directives, &directive)
   452  			continue
   453  		}
   454  		// `5-20/2`
   455  		pairs = makeLayoutRegexp(layoutRangeAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal)
   456  		if len(pairs) > 0 {
   457  			directive.kind = span
   458  			directive.first = desc.atoi(snormal[pairs[2]:pairs[3]])
   459  			directive.last = desc.atoi(snormal[pairs[4]:pairs[5]])
   460  			directive.step = atoi(snormal[pairs[6]:pairs[7]])
   461  			if directive.step < 1 || directive.step > desc.max {
   462  				return nil, fmt.Errorf("invalid interval %s", snormal)
   463  			}
   464  			directives = append(directives, &directive)
   465  			continue
   466  		}
   467  		// No behavior for this one, let caller deal with it
   468  		directive.kind = none
   469  		directives = append(directives, &directive)
   470  	}
   471  	return directives, nil
   472  }
   473  
   474  func makeLayoutRegexp(layout, value string) *regexp.Regexp {
   475  	layoutRegexpLock.Lock()
   476  	defer layoutRegexpLock.Unlock()
   477  
   478  	layout = strings.Replace(layout, `%value%`, value, -1)
   479  	re := layoutRegexp[layout]
   480  	if re == nil {
   481  		re = regexp.MustCompile(layout)
   482  		layoutRegexp[layout] = re
   483  	}
   484  	return re
   485  }
   486  
   487  func ParseNextTime(cronLine string) (nextTime time.Time, err error) {
   488  	expr, err := Parse(cronLine)
   489  	if err == nil {
   490  		nextTime = expr.Next(time.Now())
   491  	}
   492  
   493  	return
   494  }
   495  
   496  func Parse(cronLine string) (*Expression, error) {
   497  	cron := cronNormalizer.Replace(cronLine)
   498  
   499  	indices := fieldFinder.FindAllStringIndex(cron, -1)
   500  	fieldCount := len(indices)
   501  	if fieldCount < 5 {
   502  		return nil, fmt.Errorf("missing field(s)")
   503  	}
   504  	// ignore fields beyond 7th
   505  	if fieldCount > 7 {
   506  		fieldCount = 7
   507  	}
   508  
   509  	var expr = Expression{}
   510  	var field = 0
   511  	var err error
   512  
   513  	// second field (optional)
   514  	if fieldCount == 7 {
   515  		err = expr.secondFieldHandler(cron[indices[field][0]:indices[field][1]])
   516  		if err != nil {
   517  			return nil, err
   518  		}
   519  		field += 1
   520  	} else {
   521  		expr.secondList = []int{0}
   522  	}
   523  
   524  	// minute field
   525  	err = expr.minuteFieldHandler(cron[indices[field][0]:indices[field][1]])
   526  	if err != nil {
   527  		return nil, err
   528  	}
   529  	field += 1
   530  
   531  	// hour field
   532  	err = expr.hourFieldHandler(cron[indices[field][0]:indices[field][1]])
   533  	if err != nil {
   534  		return nil, err
   535  	}
   536  	field += 1
   537  
   538  	// day of month field
   539  	err = expr.domFieldHandler(cron[indices[field][0]:indices[field][1]])
   540  	if err != nil {
   541  		return nil, err
   542  	}
   543  	field += 1
   544  
   545  	// month field
   546  	err = expr.monthFieldHandler(cron[indices[field][0]:indices[field][1]])
   547  	if err != nil {
   548  		return nil, err
   549  	}
   550  	field += 1
   551  
   552  	// day of week field
   553  	err = expr.dowFieldHandler(cron[indices[field][0]:indices[field][1]])
   554  	if err != nil {
   555  		return nil, err
   556  	}
   557  	field += 1
   558  
   559  	// year field
   560  	if field < fieldCount {
   561  		err = expr.yearFieldHandler(cron[indices[field][0]:indices[field][1]])
   562  		if err != nil {
   563  			return nil, err
   564  		}
   565  	} else {
   566  		expr.yearList = yearDescriptor.defaultList
   567  	}
   568  
   569  	return &expr, nil
   570  }
   571  
   572  // Next returns the closest time instant immediately following `fromTime` which matches the cron expression `expr`
   573  func (expr *Expression) Next(fromTime time.Time) time.Time {
   574  	// Special case
   575  	if fromTime.IsZero() {
   576  		return fromTime
   577  	}
   578  
   579  	// year
   580  	v := fromTime.Year()
   581  	i := sort.SearchInts(expr.yearList, v)
   582  	if i == len(expr.yearList) {
   583  		return time.Time{}
   584  	}
   585  	if v != expr.yearList[i] {
   586  		return expr.nextYear(fromTime)
   587  	}
   588  	// month
   589  	v = int(fromTime.Month())
   590  	i = sort.SearchInts(expr.monthList, v)
   591  	if i == len(expr.monthList) {
   592  		return expr.nextYear(fromTime)
   593  	}
   594  	if v != expr.monthList[i] {
   595  		return expr.nextMonth(fromTime)
   596  	}
   597  
   598  	expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(fromTime.Year(), int(fromTime.Month()))
   599  	if len(expr.actualDaysOfMonthList) == 0 {
   600  		return expr.nextMonth(fromTime)
   601  	}
   602  
   603  	// day of month
   604  	v = fromTime.Day()
   605  	i = sort.SearchInts(expr.actualDaysOfMonthList, v)
   606  	if i == len(expr.actualDaysOfMonthList) {
   607  		return expr.nextMonth(fromTime)
   608  	}
   609  	if v != expr.actualDaysOfMonthList[i] {
   610  		return expr.nextDayOfMonth(fromTime)
   611  	}
   612  	// hour
   613  	v = fromTime.Hour()
   614  	i = sort.SearchInts(expr.hourList, v)
   615  	if i == len(expr.hourList) {
   616  		return expr.nextDayOfMonth(fromTime)
   617  	}
   618  	if v != expr.hourList[i] {
   619  		return expr.nextHour(fromTime)
   620  	}
   621  	// minute
   622  	v = fromTime.Minute()
   623  	i = sort.SearchInts(expr.minuteList, v)
   624  	if i == len(expr.minuteList) {
   625  		return expr.nextHour(fromTime)
   626  	}
   627  	if v != expr.minuteList[i] {
   628  		return expr.nextMinute(fromTime)
   629  	}
   630  	// second
   631  	v = fromTime.Second()
   632  	i = sort.SearchInts(expr.secondList, v)
   633  	if i == len(expr.secondList) {
   634  		return expr.nextMinute(fromTime)
   635  	}
   636  
   637  	// If we reach this point, there is nothing better to do
   638  	// than to move to the next second
   639  
   640  	return expr.nextSecond(fromTime)
   641  }
   642  
   643  // NextN returns a slice of `n` closest time instants immediately following `fromTime` which match the cron expression `expr`
   644  func (expr *Expression) NextN(fromTime time.Time, n uint) []time.Time {
   645  	nextTimes := make([]time.Time, 0, n)
   646  	if n > 0 {
   647  		fromTime = expr.Next(fromTime)
   648  		for {
   649  			if fromTime.IsZero() {
   650  				break
   651  			}
   652  			nextTimes = append(nextTimes, fromTime)
   653  			n -= 1
   654  			if n == 0 {
   655  				break
   656  			}
   657  			fromTime = expr.nextSecond(fromTime)
   658  		}
   659  	}
   660  	return nextTimes
   661  }
   662  
   663  func (expr *Expression) nextYear(t time.Time) time.Time {
   664  	// Find index at which item in list is greater or equal to
   665  	// candidate year
   666  	i := sort.SearchInts(expr.yearList, t.Year()+1)
   667  	if i == len(expr.yearList) {
   668  		return time.Time{}
   669  	}
   670  	// Year changed, need to recalculate actual days of month
   671  	expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(expr.yearList[i], expr.monthList[0])
   672  	if len(expr.actualDaysOfMonthList) == 0 {
   673  		return expr.nextMonth(time.Date(
   674  			expr.yearList[i],
   675  			time.Month(expr.monthList[0]),
   676  			1,
   677  			expr.hourList[0],
   678  			expr.minuteList[0],
   679  			expr.secondList[0],
   680  			0,
   681  			t.Location()))
   682  	}
   683  	return time.Date(
   684  		expr.yearList[i],
   685  		time.Month(expr.monthList[0]),
   686  		expr.actualDaysOfMonthList[0],
   687  		expr.hourList[0],
   688  		expr.minuteList[0],
   689  		expr.secondList[0],
   690  		0,
   691  		t.Location())
   692  }
   693  
   694  func (expr *Expression) nextMonth(t time.Time) time.Time {
   695  	// Find index at which item in list is greater or equal to
   696  	// candidate month
   697  	i := sort.SearchInts(expr.monthList, int(t.Month())+1)
   698  	if i == len(expr.monthList) {
   699  		return expr.nextYear(t)
   700  	}
   701  	// Month changed, need to recalculate actual days of month
   702  	expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(t.Year(), expr.monthList[i])
   703  	if len(expr.actualDaysOfMonthList) == 0 {
   704  		return expr.nextMonth(time.Date(
   705  			t.Year(),
   706  			time.Month(expr.monthList[i]),
   707  			1,
   708  			expr.hourList[0],
   709  			expr.minuteList[0],
   710  			expr.secondList[0],
   711  			0,
   712  			t.Location()))
   713  	}
   714  
   715  	return time.Date(
   716  		t.Year(),
   717  		time.Month(expr.monthList[i]),
   718  		expr.actualDaysOfMonthList[0],
   719  		expr.hourList[0],
   720  		expr.minuteList[0],
   721  		expr.secondList[0],
   722  		0,
   723  		t.Location())
   724  }
   725  
   726  func (expr *Expression) nextDayOfMonth(t time.Time) time.Time {
   727  	// Find index at which item in list is greater or equal to
   728  	// candidate day of month
   729  	i := sort.SearchInts(expr.actualDaysOfMonthList, t.Day()+1)
   730  	if i == len(expr.actualDaysOfMonthList) {
   731  		return expr.nextMonth(t)
   732  	}
   733  
   734  	return time.Date(
   735  		t.Year(),
   736  		t.Month(),
   737  		expr.actualDaysOfMonthList[i],
   738  		expr.hourList[0],
   739  		expr.minuteList[0],
   740  		expr.secondList[0],
   741  		0,
   742  		t.Location())
   743  }
   744  
   745  func (expr *Expression) nextHour(t time.Time) time.Time {
   746  	// Find index at which item in list is greater or equal to
   747  	// candidate hour
   748  	i := sort.SearchInts(expr.hourList, t.Hour()+1)
   749  	if i == len(expr.hourList) {
   750  		return expr.nextDayOfMonth(t)
   751  	}
   752  
   753  	return time.Date(
   754  		t.Year(),
   755  		t.Month(),
   756  		t.Day(),
   757  		expr.hourList[i],
   758  		expr.minuteList[0],
   759  		expr.secondList[0],
   760  		0,
   761  		t.Location())
   762  }
   763  
   764  func (expr *Expression) nextMinute(t time.Time) time.Time {
   765  	// Find index at which item in list is greater or equal to
   766  	// candidate minute
   767  	i := sort.SearchInts(expr.minuteList, t.Minute()+1)
   768  	if i == len(expr.minuteList) {
   769  		return expr.nextHour(t)
   770  	}
   771  
   772  	return time.Date(
   773  		t.Year(),
   774  		t.Month(),
   775  		t.Day(),
   776  		t.Hour(),
   777  		expr.minuteList[i],
   778  		expr.secondList[0],
   779  		0,
   780  		t.Location())
   781  }
   782  
   783  func (expr *Expression) nextSecond(t time.Time) time.Time {
   784  	// nextSecond() assumes all other fields are exactly matched
   785  	// to the cron expression
   786  
   787  	// Find index at which item in list is greater or equal to
   788  	// candidate second
   789  	i := sort.SearchInts(expr.secondList, t.Second()+1)
   790  	if i == len(expr.secondList) {
   791  		return expr.nextMinute(t)
   792  	}
   793  
   794  	return time.Date(
   795  		t.Year(),
   796  		t.Month(),
   797  		t.Day(),
   798  		t.Hour(),
   799  		t.Minute(),
   800  		expr.secondList[i],
   801  		0,
   802  		t.Location())
   803  }
   804  
   805  func (expr *Expression) calculateActualDaysOfMonth(year, month int) []int {
   806  	actualDaysOfMonthMap := make(map[int]bool)
   807  	firstDayOfMonth := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
   808  	lastDayOfMonth := firstDayOfMonth.AddDate(0, 1, -1)
   809  
   810  	// As per crontab man page (http://linux.die.net/man/5/crontab#):
   811  	//  "The day of a command's execution can be specified by two
   812  	//  "fields - day of month, and day of week. If both fields are
   813  	//  "restricted (ie, aren't *), the command will be run when
   814  	//  "either field matches the current time"
   815  
   816  	// If both fields are not restricted, all days of the month are a hit
   817  	if !expr.daysOfMonthRestricted && !expr.daysOfWeekRestricted {
   818  		return genericDefaultList[1 : lastDayOfMonth.Day()+1]
   819  	}
   820  
   821  	// day-of-month != `*`
   822  	if expr.daysOfMonthRestricted {
   823  		// Last day of month
   824  		if expr.lastDayOfMonth {
   825  			actualDaysOfMonthMap[lastDayOfMonth.Day()] = true
   826  		}
   827  		// Last work day of month
   828  		if expr.lastWorkdayOfMonth {
   829  			actualDaysOfMonthMap[workdayOfMonth(lastDayOfMonth, lastDayOfMonth)] = true
   830  		}
   831  		// Days of month
   832  		for v := range expr.daysOfMonth {
   833  			// Ignore days beyond end of month
   834  			if v <= lastDayOfMonth.Day() {
   835  				actualDaysOfMonthMap[v] = true
   836  			}
   837  		}
   838  		// Work days of month
   839  		// As per Wikipedia: month boundaries are not crossed.
   840  		for v := range expr.workdaysOfMonth {
   841  			// Ignore days beyond end of month
   842  			if v <= lastDayOfMonth.Day() {
   843  				actualDaysOfMonthMap[workdayOfMonth(firstDayOfMonth.AddDate(0, 0, v-1), lastDayOfMonth)] = true
   844  			}
   845  		}
   846  	}
   847  
   848  	// day-of-week != `*`
   849  	if expr.daysOfWeekRestricted {
   850  		// How far first sunday is from first day of month
   851  		offset := 7 - int(firstDayOfMonth.Weekday())
   852  		// days of week
   853  		//  offset : (7 - day_of_week_of_1st_day_of_month)
   854  		//  target : 1 + (7 * week_of_month) + (offset + day_of_week) % 7
   855  		for v := range expr.daysOfWeek {
   856  			w := dowNormalizedOffsets[(offset+v)%7]
   857  			actualDaysOfMonthMap[w[0]] = true
   858  			actualDaysOfMonthMap[w[1]] = true
   859  			actualDaysOfMonthMap[w[2]] = true
   860  			actualDaysOfMonthMap[w[3]] = true
   861  			if len(w) > 4 && w[4] <= lastDayOfMonth.Day() {
   862  				actualDaysOfMonthMap[w[4]] = true
   863  			}
   864  		}
   865  		// days of week of specific week in the month
   866  		//  offset : (7 - day_of_week_of_1st_day_of_month)
   867  		//  target : 1 + (7 * week_of_month) + (offset + day_of_week) % 7
   868  		for v := range expr.specificWeekDaysOfWeek {
   869  			v = 1 + 7*(v/7) + (offset+v)%7
   870  			if v <= lastDayOfMonth.Day() {
   871  				actualDaysOfMonthMap[v] = true
   872  			}
   873  		}
   874  		// Last days of week of the month
   875  		lastWeekOrigin := firstDayOfMonth.AddDate(0, 1, -7)
   876  		offset = 7 - int(lastWeekOrigin.Weekday())
   877  		for v := range expr.lastWeekDaysOfWeek {
   878  			v = lastWeekOrigin.Day() + (offset+v)%7
   879  			if v <= lastDayOfMonth.Day() {
   880  				actualDaysOfMonthMap[v] = true
   881  			}
   882  		}
   883  	}
   884  
   885  	return toList(actualDaysOfMonthMap)
   886  }
   887  
   888  func workdayOfMonth(targetDom, lastDom time.Time) int {
   889  	// If saturday, then friday
   890  	// If sunday, then monday
   891  	dom := targetDom.Day()
   892  	dow := targetDom.Weekday()
   893  	if dow == time.Saturday {
   894  		if dom > 1 {
   895  			dom -= 1
   896  		} else {
   897  			dom += 2
   898  		}
   899  	} else if dow == time.Sunday {
   900  		if dom < lastDom.Day() {
   901  			dom += 1
   902  		} else {
   903  			dom -= 2
   904  		}
   905  	}
   906  	return dom
   907  }