git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/cron/spec_test.go (about)

     1  package cron
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  func TestActivation(t *testing.T) {
    10  	tests := []struct {
    11  		time, spec string
    12  		expected   bool
    13  	}{
    14  		// Every fifteen minutes.
    15  		{"Mon Jul 9 15:00 2012", "0/15 * * * *", true},
    16  		{"Mon Jul 9 15:45 2012", "0/15 * * * *", true},
    17  		{"Mon Jul 9 15:40 2012", "0/15 * * * *", false},
    18  
    19  		// Every fifteen minutes, starting at 5 minutes.
    20  		{"Mon Jul 9 15:05 2012", "5/15 * * * *", true},
    21  		{"Mon Jul 9 15:20 2012", "5/15 * * * *", true},
    22  		{"Mon Jul 9 15:50 2012", "5/15 * * * *", true},
    23  
    24  		// Named months
    25  		{"Sun Jul 15 15:00 2012", "0/15 * * Jul *", true},
    26  		{"Sun Jul 15 15:00 2012", "0/15 * * Jun *", false},
    27  
    28  		// Everything set.
    29  		{"Sun Jul 15 08:30 2012", "30 08 ? Jul Sun", true},
    30  		{"Sun Jul 15 08:30 2012", "30 08 15 Jul ?", true},
    31  		{"Mon Jul 16 08:30 2012", "30 08 ? Jul Sun", false},
    32  		{"Mon Jul 16 08:30 2012", "30 08 15 Jul ?", false},
    33  
    34  		// Predefined schedules
    35  		{"Mon Jul 9 15:00 2012", "@hourly", true},
    36  		{"Mon Jul 9 15:04 2012", "@hourly", false},
    37  		{"Mon Jul 9 15:00 2012", "@daily", false},
    38  		{"Mon Jul 9 00:00 2012", "@daily", true},
    39  		{"Mon Jul 9 00:00 2012", "@weekly", false},
    40  		{"Sun Jul 8 00:00 2012", "@weekly", true},
    41  		{"Sun Jul 8 01:00 2012", "@weekly", false},
    42  		{"Sun Jul 8 00:00 2012", "@monthly", false},
    43  		{"Sun Jul 1 00:00 2012", "@monthly", true},
    44  
    45  		// Test interaction of DOW and DOM.
    46  		// If both are restricted, then only one needs to match.
    47  		{"Sun Jul 15 00:00 2012", "* * 1,15 * Sun", true},
    48  		{"Fri Jun 15 00:00 2012", "* * 1,15 * Sun", true},
    49  		{"Wed Aug 1 00:00 2012", "* * 1,15 * Sun", true},
    50  		{"Sun Jul 15 00:00 2012", "* * */10 * Sun", true}, // verifies #70
    51  
    52  		// However, if one has a star, then both need to match.
    53  		{"Sun Jul 15 00:00 2012", "* * * * Mon", false},
    54  		{"Mon Jul 9 00:00 2012", "* * 1,15 * *", false},
    55  		{"Sun Jul 15 00:00 2012", "* * 1,15 * *", true},
    56  		{"Sun Jul 15 00:00 2012", "* * */2 * Sun", true},
    57  	}
    58  
    59  	for _, test := range tests {
    60  		sched, err := ParseStandard(test.spec)
    61  		if err != nil {
    62  			t.Error(err)
    63  			continue
    64  		}
    65  		actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
    66  		expected := getTime(test.time)
    67  		if test.expected && expected != actual || !test.expected && expected == actual {
    68  			t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
    69  				test.spec, test.time, expected, actual)
    70  		}
    71  	}
    72  }
    73  
    74  func TestNext(t *testing.T) {
    75  	runs := []struct {
    76  		time, spec string
    77  		expected   string
    78  	}{
    79  		// Simple cases
    80  		{"Mon Jul 9 14:45 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"},
    81  		{"Mon Jul 9 14:59 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"},
    82  		{"Mon Jul 9 14:59:59 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"},
    83  
    84  		// Wrap around hours
    85  		{"Mon Jul 9 15:45 2012", "0 20-35/15 * * * *", "Mon Jul 9 16:20 2012"},
    86  
    87  		// Wrap around days
    88  		{"Mon Jul 9 23:46 2012", "0 */15 * * * *", "Tue Jul 10 00:00 2012"},
    89  		{"Mon Jul 9 23:45 2012", "0 20-35/15 * * * *", "Tue Jul 10 00:20 2012"},
    90  		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * * *", "Tue Jul 10 00:20:15 2012"},
    91  		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * * *", "Tue Jul 10 01:20:15 2012"},
    92  		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * * *", "Tue Jul 10 10:20:15 2012"},
    93  
    94  		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"},
    95  		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"},
    96  		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"},
    97  
    98  		// Wrap around months
    99  		{"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"},
   100  		{"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Tue Aug 1 00:00 2012"},
   101  		{"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"},
   102  
   103  		// Wrap around years
   104  		{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"},
   105  		{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"},
   106  
   107  		// Wrap around minute, hour, day, month, and year
   108  		{"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"},
   109  
   110  		// Leap year
   111  		{"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"},
   112  
   113  		// Daylight savings time 2am EST (-5) -> 3am EDT (-4)
   114  		{"2012-03-11T00:00:00-0500", "TZ=America/New_York 0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"},
   115  
   116  		// hourly job
   117  		{"2012-03-11T00:00:00-0500", "TZ=America/New_York 0 0 * * * ?", "2012-03-11T01:00:00-0500"},
   118  		{"2012-03-11T01:00:00-0500", "TZ=America/New_York 0 0 * * * ?", "2012-03-11T03:00:00-0400"},
   119  		{"2012-03-11T03:00:00-0400", "TZ=America/New_York 0 0 * * * ?", "2012-03-11T04:00:00-0400"},
   120  		{"2012-03-11T04:00:00-0400", "TZ=America/New_York 0 0 * * * ?", "2012-03-11T05:00:00-0400"},
   121  
   122  		// hourly job using CRON_TZ
   123  		{"2012-03-11T00:00:00-0500", "CRON_TZ=America/New_York 0 0 * * * ?", "2012-03-11T01:00:00-0500"},
   124  		{"2012-03-11T01:00:00-0500", "CRON_TZ=America/New_York 0 0 * * * ?", "2012-03-11T03:00:00-0400"},
   125  		{"2012-03-11T03:00:00-0400", "CRON_TZ=America/New_York 0 0 * * * ?", "2012-03-11T04:00:00-0400"},
   126  		{"2012-03-11T04:00:00-0400", "CRON_TZ=America/New_York 0 0 * * * ?", "2012-03-11T05:00:00-0400"},
   127  
   128  		// 1am nightly job
   129  		{"2012-03-11T00:00:00-0500", "TZ=America/New_York 0 0 1 * * ?", "2012-03-11T01:00:00-0500"},
   130  		{"2012-03-11T01:00:00-0500", "TZ=America/New_York 0 0 1 * * ?", "2012-03-12T01:00:00-0400"},
   131  
   132  		// 2am nightly job (skipped)
   133  		{"2012-03-11T00:00:00-0500", "TZ=America/New_York 0 0 2 * * ?", "2012-03-12T02:00:00-0400"},
   134  
   135  		// Daylight savings time 2am EDT (-4) => 1am EST (-5)
   136  		{"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"},
   137  		{"2012-11-04T01:45:00-0400", "TZ=America/New_York 0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"},
   138  
   139  		// hourly job
   140  		{"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 0 * * * ?", "2012-11-04T01:00:00-0400"},
   141  		{"2012-11-04T01:00:00-0400", "TZ=America/New_York 0 0 * * * ?", "2012-11-04T01:00:00-0500"},
   142  		{"2012-11-04T01:00:00-0500", "TZ=America/New_York 0 0 * * * ?", "2012-11-04T02:00:00-0500"},
   143  
   144  		// 1am nightly job (runs twice)
   145  		{"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 0 1 * * ?", "2012-11-04T01:00:00-0400"},
   146  		{"2012-11-04T01:00:00-0400", "TZ=America/New_York 0 0 1 * * ?", "2012-11-04T01:00:00-0500"},
   147  		{"2012-11-04T01:00:00-0500", "TZ=America/New_York 0 0 1 * * ?", "2012-11-05T01:00:00-0500"},
   148  
   149  		// 2am nightly job
   150  		{"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 0 2 * * ?", "2012-11-04T02:00:00-0500"},
   151  		{"2012-11-04T02:00:00-0500", "TZ=America/New_York 0 0 2 * * ?", "2012-11-05T02:00:00-0500"},
   152  
   153  		// 3am nightly job
   154  		{"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 0 3 * * ?", "2012-11-04T03:00:00-0500"},
   155  		{"2012-11-04T03:00:00-0500", "TZ=America/New_York 0 0 3 * * ?", "2012-11-05T03:00:00-0500"},
   156  
   157  		// hourly job
   158  		{"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0400"},
   159  		{"TZ=America/New_York 2012-11-04T01:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0500"},
   160  		{"TZ=America/New_York 2012-11-04T01:00:00-0500", "0 0 * * * ?", "2012-11-04T02:00:00-0500"},
   161  
   162  		// 1am nightly job (runs twice)
   163  		{"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0400"},
   164  		{"TZ=America/New_York 2012-11-04T01:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0500"},
   165  		{"TZ=America/New_York 2012-11-04T01:00:00-0500", "0 0 1 * * ?", "2012-11-05T01:00:00-0500"},
   166  
   167  		// 2am nightly job
   168  		{"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 2 * * ?", "2012-11-04T02:00:00-0500"},
   169  		{"TZ=America/New_York 2012-11-04T02:00:00-0500", "0 0 2 * * ?", "2012-11-05T02:00:00-0500"},
   170  
   171  		// 3am nightly job
   172  		{"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 3 * * ?", "2012-11-04T03:00:00-0500"},
   173  		{"TZ=America/New_York 2012-11-04T03:00:00-0500", "0 0 3 * * ?", "2012-11-05T03:00:00-0500"},
   174  
   175  		// Unsatisfiable
   176  		{"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""},
   177  		{"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""},
   178  
   179  		// Monthly job
   180  		{"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 3 3 * ?", "2012-12-03T03:00:00-0500"},
   181  
   182  		// Test the scenario of DST resulting in midnight not being a valid time.
   183  		// https://github.com/robfig/cron/issues/157
   184  		{"2018-10-17T05:00:00-0400", "TZ=America/Sao_Paulo 0 0 9 10 * ?", "2018-11-10T06:00:00-0500"},
   185  		{"2018-02-14T05:00:00-0500", "TZ=America/Sao_Paulo 0 0 9 22 * ?", "2018-02-22T07:00:00-0500"},
   186  	}
   187  
   188  	for _, c := range runs {
   189  		sched, err := secondParser.Parse(c.spec)
   190  		if err != nil {
   191  			t.Error(err)
   192  			continue
   193  		}
   194  		actual := sched.Next(getTime(c.time))
   195  		expected := getTime(c.expected)
   196  		if !actual.Equal(expected) {
   197  			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
   198  		}
   199  	}
   200  }
   201  
   202  func TestErrors(t *testing.T) {
   203  	invalidSpecs := []string{
   204  		"xyz",
   205  		"60 0 * * *",
   206  		"0 60 * * *",
   207  		"0 0 * * XYZ",
   208  	}
   209  	for _, spec := range invalidSpecs {
   210  		_, err := ParseStandard(spec)
   211  		if err == nil {
   212  			t.Error("expected an error parsing: ", spec)
   213  		}
   214  	}
   215  }
   216  
   217  func getTime(value string) time.Time {
   218  	if value == "" {
   219  		return time.Time{}
   220  	}
   221  
   222  	var location = time.Local
   223  	if strings.HasPrefix(value, "TZ=") {
   224  		parts := strings.Fields(value)
   225  		loc, err := time.LoadLocation(parts[0][len("TZ="):])
   226  		if err != nil {
   227  			panic("could not parse location:" + err.Error())
   228  		}
   229  		location = loc
   230  		value = parts[1]
   231  	}
   232  
   233  	var layouts = []string{
   234  		"Mon Jan 2 15:04 2006",
   235  		"Mon Jan 2 15:04:05 2006",
   236  	}
   237  	for _, layout := range layouts {
   238  		if t, err := time.ParseInLocation(layout, value, location); err == nil {
   239  			return t
   240  		}
   241  	}
   242  	if t, err := time.ParseInLocation("2006-01-02T15:04:05-0700", value, location); err == nil {
   243  		return t
   244  	}
   245  	panic("could not parse time value " + value)
   246  }
   247  
   248  func TestNextWithTz(t *testing.T) {
   249  	runs := []struct {
   250  		time, spec string
   251  		expected   string
   252  	}{
   253  		// Failing tests
   254  		{"2016-01-03T13:09:03+0530", "14 14 * * *", "2016-01-03T14:14:00+0530"},
   255  		{"2016-01-03T04:09:03+0530", "14 14 * * ?", "2016-01-03T14:14:00+0530"},
   256  
   257  		// Passing tests
   258  		{"2016-01-03T14:09:03+0530", "14 14 * * *", "2016-01-03T14:14:00+0530"},
   259  		{"2016-01-03T14:00:00+0530", "14 14 * * ?", "2016-01-03T14:14:00+0530"},
   260  	}
   261  	for _, c := range runs {
   262  		sched, err := ParseStandard(c.spec)
   263  		if err != nil {
   264  			t.Error(err)
   265  			continue
   266  		}
   267  		actual := sched.Next(getTimeTZ(c.time))
   268  		expected := getTimeTZ(c.expected)
   269  		if !actual.Equal(expected) {
   270  			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
   271  		}
   272  	}
   273  }
   274  
   275  func getTimeTZ(value string) time.Time {
   276  	if value == "" {
   277  		return time.Time{}
   278  	}
   279  	t, err := time.Parse("Mon Jan 2 15:04 2006", value)
   280  	if err != nil {
   281  		t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
   282  		if err != nil {
   283  			t, err = time.Parse("2006-01-02T15:04:05-0700", value)
   284  			if err != nil {
   285  				panic(err)
   286  			}
   287  		}
   288  	}
   289  
   290  	return t
   291  }
   292  
   293  // https://github.com/robfig/cron/issues/144
   294  func TestSlash0NoHang(t *testing.T) {
   295  	schedule := "TZ=America/New_York 15/0 * * * *"
   296  	_, err := ParseStandard(schedule)
   297  	if err == nil {
   298  		t.Error("expected an error on 0 increment")
   299  	}
   300  }